讀Hadoop3.2源碼,深入了解java調(diào)用HDFS的常用操作和HDFS原理
本文將通過一個(gè)演示工程來快速上手java調(diào)用HDFS的常見操作。接下來以創(chuàng)建文件為例,通過閱讀HDFS的源碼,一步步展開HDFS相關(guān)原理、理論知識的說明。
說明:本文檔基于最新版本Hadoop3.2.1
目錄:
一、java調(diào)用HDFS的常見操作
1.1、演示環(huán)境搭建
1.2、操作HDFS
1.3、java文件操作常用方法
二、深入了解HDFS寫文件的流程和HDFS原理
2.1、Hadoop3.2.1 源碼下載及介紹
2.2、文件系統(tǒng):FileSystem
2.3、HDFS體系結(jié)構(gòu):namenode、datanode、數(shù)據(jù)塊
2.4、如何訪問阿里云OSS等文件系統(tǒng)
2.5、文件租約機(jī)制
2.6、RPC機(jī)制
2.7、HAState:active、standby
2.8、Hadoop3.x新特性:糾刪碼
2.9、文件透明加密處理和目錄樹
2.10、HDFS客戶端寫流程總結(jié)
一、java調(diào)用HDFS的常見操作
首先我們搭建一個(gè)簡單的演示工程(演示工程使用的gradle,Maven項(xiàng)目也同樣添加以下依賴),本次使用的是Hadoop最新的3.2.1。
1.1、演示環(huán)境搭建
新增一個(gè)普通的java工程即可,過程略,添加hdfs相關(guān)依賴jar包
implementation ('org.apache.hadoop:hadoop-common:3.2.1')
implementation ('org.apache.hadoop:hadoop-hdfs:3.2.1')
implementation ('org.apache.hadoop:hadoop-mapreduce-client-core:3.2.1')
implementation ('org.apache.hadoop:hadoop-client:3.2.1')在實(shí)際運(yùn)行過程中,可能會發(fā)現(xiàn)日志Jar包沖突問題,排除掉即可
exclude group:'org.slf4j',module: 'slf4j-log4j12'1.2、操作HDFS
以創(chuàng)建文件為例,代碼如下。可以看到j(luò)ava操作hdfs就是這么簡單、絲滑,so easy!
publicstaticvoidmain(String[] args)throws IOException {
// 配置對象
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS", "hdfs://172.22.28.202:9000");
// HDFS文件系統(tǒng)的操作對象
FileSystem fileSystem = FileSystem.get(configuration);
// 創(chuàng)建文件。
FSDataOutputStream outputStream =
fileSystem.create(new Path("/hdfs/madashu/test"));
// 寫入文件內(nèi)容
outputStream.write("你好Hadoop,我是碼大叔".getBytes());
outputStream.flush();
IOUtils.closeStream(outputStream);
}1.3、java文件操作常用方法
參照第2步文件創(chuàng)建的操作,我們可以預(yù)定義好Configuration和FileSystem,然后提取出HDFSUtil的工具類出來。涉及到文件方面的操作基本只需要hadoop-common包下的FileSystem就足夠了,一些常用方法的說明:
//文件是否存在
fileSystem.exists(new Path(fileName));
//創(chuàng)建目錄
fileSystem.mkdirs(new Path(directorName));
//刪除目錄或文件,第二個(gè)參數(shù)表示是否要遞歸刪除
fileSystem.delete(new Path(name), true);
//獲取當(dāng)前登錄用戶在HDFS文件系統(tǒng)中的Home目錄
fileSystem.getHomeDirectory();
//文件重命名
fileSystem.rename(new Path(oldName), new Path(newName));
//讀取文件,返回的是FSDataInputStream
fileSystem.open(new Path(fileName));
//創(chuàng)建文件,第二個(gè)參數(shù)表示文件存在時(shí)是否覆蓋
fileSystem.create(new Path(fileName), false);
//從本地目錄上傳文件到HDFS
fileSystem.copyFromLocalFile(localPath, hdfsPath);
//獲取目錄下的文件信息,包含path,length,group,blocksize,permission等等
fileSystem.listStatus(new Path(directorName));
//釋放資源
fileSystem.close();
//設(shè)置HDFS資源權(quán)限,其中FsPermission可以設(shè)置user、group等
fileSystem.setPermission(new Path(resourceName), fsPermission);
//設(shè)置HDFS資源的Owner和group
fileSystem.setOwner(new Path(resourceName), ownerName, groupName);
//設(shè)置文件的副本
fileSystem.setReplication(new Path(resourceName), count);二、深入了解HDFS寫文件的流程和HDFS原理
文件操作的方法比較多,本期我們以create方法為例,來通過閱讀源碼深入了解下hdfs寫文件的流程和原理,代碼參見1.2 。
2.1、Hadoop3.2.1 源碼下載及介紹
hadoop源碼地址:https://github.com/apache/hadoop,。
正常途徑下訪問比較慢的同學(xué)(每次寫到這句話,都滿臉的憂傷和xx)也可以通過國內(nèi)的清華大學(xué)開源軟件鏡像站來下載,地址是https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/common/hadoop-3.2.1/hadoop-3.2.1-src.tar.gz
下載后我們可以看到這是一個(gè)maven工程,導(dǎo)入到idea等我們熟悉開發(fā)工具中即可。如果是使用VS需要編譯的小伙伴注意下,
目錄下有一個(gè)BUILDINDG.txt文件,針對比較關(guān)鍵的幾個(gè)modules做了說明。
這里面很多工程都是和打包相關(guān)的,有一個(gè)沒提到的“hadoop-cloud-storage-project”是和云存儲相關(guān)的,比如我們熟悉的阿里云,AWS等。這次我們需要關(guān)注的是hadoop-hdfs-project,hadoop-hdfs-common-project。
2.2、文件系統(tǒng):FileSystem
代碼參見1.2,我們看到在操作hdfs之前首先需要根據(jù)配置文件獲取文件系統(tǒng)。
問題:
1、為什么傳入的地址是“hdfs:”開頭的
2、為什么要獲取文件操作系統(tǒng)
我們直接進(jìn)入get方法
publicstatic FileSystem get(URI uri, Configuration conf)throws IOException {
//獲取文件的前綴,即我們傳入的 hdfs:
String scheme = uri.getScheme();
// 為了便于閱讀,刪除掉很多代碼
// 從緩存中獲取
return CACHE.get(uri, conf);
}那么緩存中存放了什么呢?一層層深入代碼,首先會檢查文件系統(tǒng)是否存在,不存在則創(chuàng)建文件系統(tǒng),最終將文件系統(tǒng)存放在map中。
private static final Map>
SERVICE_FILE_SYSTEMS = new HashMap<>(); public final classHdfsConstants{
/**
* URI Scheme for hdfs://namenode/ URIs.
*/
public static final String HDFS_URI_SCHEME = "hdfs";我們再回過頭來打開FileSystem類
public abstract classFileSystemextendsConfiguredimplementsCloseable, DelegationTokenIssuer可以看到FileSystem是一個(gè)抽象類,它有很多的子類即實(shí)現(xiàn),比如DistributedFileSystem。所以這一步的操作實(shí)際是根據(jù)你輸入的前綴,通過Java中SPI機(jī)制從Serviceloder中獲取所需的文件操作系統(tǒng)。這里我們還很驚喜地看到AliyunOSSFileSystem。Hadoop3.x中默認(rèn)支持阿里云OSS對象存儲系統(tǒng)作為Hadoop兼容的文件系統(tǒng)。阿里云OSS是中國云計(jì)算廠商第一個(gè)也是目前唯一一個(gè)被Hadoop官方版本支持的云存儲系統(tǒng)。這是繼Docker支持阿里云存儲以后又一個(gè)更重大的里程碑,這也表明主流開源社區(qū)對中國技術(shù)生態(tài)的認(rèn)可。假如我們要使用阿里云的文件系統(tǒng),前綴是什么呢?翻看AliyunOSSFileSystem代碼
public String getScheme(){
return "oss";
}比如 oss://madashu/test。同樣如果需要使用亞馬遜的文件系統(tǒng),則前綴是“abfs://”
2.3、HDFS體系結(jié)構(gòu):namenode、datanode、數(shù)據(jù)塊
根據(jù)1.2實(shí)例代碼,獲取到文件操作系統(tǒng)后,就是創(chuàng)建文件了,最終我們跟蹤到如下的方法
publicabstract FSDataOutputStream create(Path f,
FsPermission permission,
boolean
int
short
long
throws IOException;參數(shù)說明:
Path:存放路徑
FsPermission:文件權(quán)限
overwrite:當(dāng)文件存在時(shí)是否覆蓋
bufferSize:客戶端的buffer大小
replication:文件副本數(shù)
blockSize:塊大小
Progressable:文件寫入的進(jìn)度
這里有2個(gè)參數(shù):replication和blockSize,在解釋之前得先了解一下HDFS的體系結(jié)構(gòu)
HDFS是一個(gè)主/從(Master/Slave)體系結(jié)構(gòu)的分布式系統(tǒng),將一個(gè)大文件分成若干塊保存在不同服務(wù)器的多個(gè)節(jié)點(diǎn)中,通過聯(lián)網(wǎng)讓用戶感覺像是在本地一樣查看文件。HDFS集群擁有1個(gè)Namenode和n個(gè)Datanode,用戶可以通過HDFS客戶端和Namenode、Datanodes交互以訪問文件系統(tǒng)。
Namenode是HDFS的master節(jié)點(diǎn),負(fù)責(zé)管理文件系統(tǒng)的命名空間,即namespace,它維護(hù)這文件系統(tǒng)樹及整棵樹內(nèi)所有的文件和目錄。這些信息以命名空間鏡像文件和編輯日志文件兩個(gè)文件持久化保存在文件磁盤上。namenode也留著每個(gè)文件中各個(gè)塊所在的數(shù)據(jù)節(jié)點(diǎn)信息,但是并不永久保存塊的位置信息,這些塊的位置信息會在系統(tǒng)啟動時(shí)根據(jù)數(shù)據(jù)信息節(jié)點(diǎn)創(chuàng)建。
Datanode是文件系統(tǒng)的工作節(jié)點(diǎn),它根據(jù)客戶端或namenode需要存儲并檢索數(shù)據(jù)塊,并且定期向nomenode發(fā)送所存儲的塊的列表。
Block是HDFS的最小存儲單元。默認(rèn)大?。?28M(HDFS 1.x中,默認(rèn)64M),若文件大小不足128M,則會單獨(dú)成為一個(gè)block。實(shí)質(zhì)上就是Linux相應(yīng)目錄下的普通文件,名稱格式:blk_xxxxxxx。
HDFS塊為什么這么大呢?HDFS的塊比磁盤的塊大,主要是為了最小化尋址的開銷。如果塊足夠大,從磁盤傳輸數(shù)據(jù)的時(shí)間會明顯大于定位這個(gè)塊開始位置所需的時(shí)間。因而,傳輸一個(gè)由多個(gè)塊組成的大文件的時(shí)間取決于磁盤傳輸速率。如果一個(gè)1MB的文件存儲在一個(gè)128M的塊中時(shí),文件實(shí)際只是用了1M的磁盤空間,而不是128M。
為了降低文件丟失造成的錯(cuò)誤,它會為每個(gè)小文件復(fù)制多個(gè)副本(默認(rèn)為三個(gè)),以此來實(shí)現(xiàn)多機(jī)器上的多用戶分享文件和存儲。
第一個(gè)復(fù)本會隨機(jī)選擇,但是不會選擇存儲過滿的節(jié)點(diǎn)。
第二個(gè)復(fù)本放在和第一個(gè)復(fù)本不同且隨機(jī)選擇的機(jī)架上。
第三個(gè)和第二個(gè)放在同一個(gè)機(jī)架上的不同節(jié)點(diǎn)上。
剩余的副本就完全隨機(jī)節(jié)點(diǎn)了。
補(bǔ)充1:create方法還有最后一個(gè)參數(shù):Progressable,主要是為了便于我們知悉文件的寫入進(jìn)度,使用方法如下:
FSDataOutputStream outputStream = fileSystem.create(new Path(targetDirector + File.separator + fileName), new Progressable() {
long fileCount = 0;
@Override
publicvoidprogress(){
fileCount++;
System.out.println("總進(jìn)度:" + fileCount + "|" + fileSize + "|" + (fileCount / fileSize) * 100 + "%");
}
});補(bǔ)充2:在Hadoop3.2中namenode的默認(rèn)端口配置發(fā)生變化:從50070改為9870
2.4、如何訪問阿里云OSS等文件系統(tǒng)
我們繼續(xù)往下扒代碼
@Override
public FSDataOutputStream create(final Path f, final
final EnumSet cflags, finalint
finalshort replication, finallong
final Progressable progress, final ChecksumOpt checksumOpt)
throws IOException {
// 文件操作統(tǒng)計(jì),比如創(chuàng)建、刪除、拷貝等等,以及操作次數(shù)
statistics.incrementWriteOps(1);
storageStatistics.incrementOpCounter(OpType.CREATE);
// 創(chuàng)建文件輸出流,采用了責(zé)任鏈的設(shè)計(jì)模式
Path absF = fixRelativePart(f);
return new FileSystemLinkResolver() {
@Override
public FSDataOutputStream doCall(final Path p)throws IOException {
final DFSOutputStream dfsos = dfs.create(getPathName(p), permission,
cflags, replication, blockSize, progress, bufferSize,
checksumOpt);
return dfs.createWrappedOutputStream(dfsos, statistics);
}
@Override
public FSDataOutputStream next(final FileSystem fs, final Path p)
throws IOException {
return fs.create(p, permission, cflags, bufferSize,
replication, blockSize, progress, checksumOpt);
}
}.resolve(this, absF);
} 接下來再進(jìn)入FileSystemLinkResolver類:
1、調(diào)用doCall 內(nèi)部類 DFSClient的create方法,然后將DFSOutputStream包裝FSDataOutputStream
2、如果是符號鏈接文件,則一層一層找到最底層的文件。甚至能連接到其他的文件系統(tǒng)的文件,比如從HDFS文件系統(tǒng)連接到阿里云OSS文件系統(tǒng)、亞馬遜文件系統(tǒng)等。
2.5、文件租約機(jī)制
繼續(xù)跟蹤代碼,進(jìn)入DFSClient類
public DFSOutputStream create
boolean createParent, short
long blockSize, Progressable progress, int buffersize,
ChecksumOpt checksumOpt, InetSocketAddress[] favoredNodes,
String ecPolicyName)throws IOException {
//檢查客戶端是否已經(jīng)在運(yùn)行了
checkOpen();
final FsPermission masked = applyUMask(permission);
LOG.debug("{}: masked={}", src, masked);
//創(chuàng)建文件輸出流,和Namenode進(jìn)行交互
final DFSOutputStream result = DFSOutputStream.newStreamForCreate(this,
src, masked, flag, createParent, replication, blockSize, progress,
dfsClientConf.createChecksum(checksumOpt),
getFavoredNodesStr(favoredNodes), ecPolicyName);
//更新文件租約:也可以理解為token,保證不會發(fā)生寫文件沖突。
beginFileLease(result.getFileId(), result);
return result;
}我們看到最后一個(gè)beginFileLease操作,也就是獲取文件租約。我們暫時(shí)先忽略文件創(chuàng)建的過程,繼續(xù)往下翻和FileLease有關(guān)的代碼:
//如果是第一次,還是設(shè)置文件租約。
stat = FSDirWriteFileOp.startFile(this, iip, permissions, holder,
clientMachine, flag, createParent, replication, blockSize, feInfo,
toRemoveBlocks, shouldReplicate, ecPolicyName, logRetryCache);//設(shè)置文件租約的方法見FSDirWriteFileOp
fsn.leaseManager.addLease(
newNode.getFileUnderConstructionFeature().getClientName(),
newNode.getId());FileLease:文件租約,HDFS給客戶端發(fā)放一個(gè)寫文件操作的臨時(shí)許可證,只有持有該證件者才允許操作此文件,從而保證保證數(shù)據(jù)的一致。
每個(gè)客戶端用戶持有一個(gè)文件租約。
每個(gè)文件租約內(nèi)部包含有一個(gè)租約持有者信息,還有租約對應(yīng)的文件Id列表,即當(dāng)前租約持有者正在寫這些文件Id對應(yīng)的文件。
每個(gè)文件租約內(nèi)包含有一個(gè)最新近更新時(shí)間,最近更新時(shí)間將會決定此租約是否已過期。過期的租約會導(dǎo)致租約持有者無法繼續(xù)執(zhí)行寫數(shù)據(jù)到文件中,除非進(jìn)行租約的更新。
既然每個(gè)客戶端都有一個(gè)文件租約,那么HDFS如如何管理的呢?比如有些客戶端用戶寫某文件后未及時(shí)關(guān)閉此文件。這樣會導(dǎo)致租約未釋放,從而造成其他用戶無法對此文件進(jìn)行寫操作。答案就是LeaseManager,運(yùn)行在Active NameNode的服務(wù)中。它主要做2件事:
1、維護(hù)HDFS內(nèi)部當(dāng)前所有的租約,
2、定期釋放過期的租約對象。
補(bǔ)充:HDFS 只允許對一個(gè)已打開的文件順序?qū)懭?,或者在現(xiàn)有文件的末尾追加數(shù)據(jù)。
2.6、RPC機(jī)制
接下來我們的代碼將進(jìn)入DFSOutputStream.newStreamForCreate()方法
//調(diào)用namenode的文件創(chuàng)建方法
stat = dfsClient.namenode.create(src, masked, dfsClient.clientName,
new EnumSetWritable<>(flag), createParent, replication,
blockSize, SUPPORTED_CRYPTO_VERSIONS, ecPolicyName)我們再次暫停一下,點(diǎn)擊“這里的namenode實(shí)際是ClientProtocol
ClientProtocol is used by user code via the DistributedFileSystem class to communicate with the NameNode. User code can manipulate the directory namespace, as well as open/close file streams, etc.
ClientProtocol用來通過DistributedFileSystem類與NameNode通信??梢圆僮髂夸浢Q空間,以及打開/關(guān)閉文件流等。ClientProtocol是一個(gè)接口,它的實(shí)現(xiàn)類有:

我們進(jìn)入NameNodeRpcServer.create()方法
@Override
public HdfsFileStatus create(String src, FsPermission masked,
String clientName, EnumSetWritable flag,
boolean createParent, short replication, long
throws IOException {
//確認(rèn)namenode已啟動
checkNNStartup();
// 獲取服務(wù)端ip
String clientMachine = getClientMachine();
if (stateChangeLog.isDebugEnabled()) {
stateChangeLog.debug("*DIR* NameNode.create: file "
+src+" for "+clientName+" at "+clientMachine);
}
//檢查是否可以寫入。在生成上namenode正常也會進(jìn)行HA,保證高可用。只有主的才可以寫入,
if (!checkPathLength(src)) {
throw new IOException("create: Pathname too long. Limit "
+ MAX_PATH_LENGTH + " characters, " + MAX_PATH_DEPTH + " levels.");
}
namesystem.checkOperation(OperationCategory.WRITE);
CacheEntryWithPayload cacheEntry = RetryCache.waitForCompletion(retryCache, null);
if (cacheEntry != null && cacheEntry.isSuccess()) {
return (HdfsFileStatus) cacheEntry.getPayload();
}作為分布式文件系統(tǒng),少不了各個(gè)節(jié)點(diǎn)之間的通信和交互,比如client和namenode,namenode和datanode,所以需要這樣一套RPC(Remote Procedure CallProtocol,遠(yuǎn)程過程調(diào)用協(xié)議)框架,允許程序像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程機(jī)器上應(yīng)用程序提供的服務(wù)。Hadoop RPC并沒有采用JDK自帶的RMI,據(jù)說基于Google Protocol Buffer(簡稱Protobuf)來實(shí)現(xiàn)的。Hadoop的RPC和通用的RPC一樣,包含通信模塊、客戶端Stub程序、服務(wù)端Stub程序、請求程序、服務(wù)程序等。

Hadoop RCP 主要提供兩個(gè)接口
//構(gòu)造一個(gè)客戶端代理對象,用于向服務(wù)器發(fā)送RPC請求
public static ProtocolProxy getProxy/waitForProxy()
// 為某個(gè)協(xié)議實(shí)例構(gòu)造一個(gè)服務(wù)器對象,用于處理客戶端發(fā)送的請求
public static Server RPC.Builder (Configuration).build() 2.7 HAState:active、standby
HdfsFileStatus status = null;
try {
PermissionStatus perm = new PermissionStatus(getRemoteUser()
.getShortUserName(), null, masked);
// 開始創(chuàng)建文件
status = namesystem.startFile(src, perm, clientName, clientMachine,
flag.get(), createParent, replication, blockSize, supportedVersions,
ecPolicyName, cacheEntry != null);
} finally {
RetryCache.setState(cacheEntry, status != null, status);
}
metrics.incrFilesCreated();
metrics.incrCreateFileOps();
return status;
} @Override
// 報(bào)錯(cuò)
publicvoidcheckOperation(final OperationCategory op)
throws StandbyException {
state.checkOperation(haContext, op);
}在這個(gè)代碼里有一個(gè)HA狀態(tài)的檢查,standby 只能read,不能write。
public static final HAState ACTIVE_STATE = new ActiveState();
public static final HAState STANDBY_STATE = new StandbyState();
public static final HAState OBSERVER_STATE = new StandbyState(true);從Hadoop2開始,增加了對HDFS高可用(HA)的支持,配置了1對active-standby的namenode。當(dāng)活動的namenode失效,備用的namenode能夠快速(幾十秒的時(shí)間)實(shí)現(xiàn)任務(wù)接管,因?yàn)樽钚碌臓顟B(tài)存儲在內(nèi)存中:包括最新的編輯日志條目和最新的數(shù)據(jù)塊映射信息。實(shí)際觀察到的失效時(shí)間略長一點(diǎn),需要1分鐘左右,這是因?yàn)橄到y(tǒng)需要保守確定活動的namenode是否真的失效了。假設(shè)活動的namenode和備用的namenode都失效了(人品爆發(fā)了),管理員依舊可以聲明一個(gè)備用namenode并實(shí)現(xiàn)冷啟動。
【實(shí)際開發(fā)踩坑】
在實(shí)際開發(fā)過程中,由于配置或者啟動順序的原因,倒是會經(jīng)查遇到standby的問題,甚至發(fā)現(xiàn)master和slave兩個(gè)NameNode的狀態(tài)均為standby。比如啟動了hdfs再啟動zookeeper 導(dǎo)致zookeeper的選舉機(jī)制zkfc(DFSZKFailoverController)沒有格式化 NameNode節(jié)點(diǎn)的自動切換機(jī)制沒有開啟 兩個(gè)NameNode都處于standby狀態(tài)(解決方案:先啟動zookeeper集群:zkServer.sh start 再啟動hdfs集群FSNamesystem)。
人工查看namenode的方法
sudo -E -u hadoop /home/hadoop/bin/hdfs haadmin -getServiceState nn12.8、Hadoop3.x新特性:糾刪碼
private HdfsFileStatus startFileInt(String src,
PermissionStatus permissions, String holder, String clientMachine,
EnumSet flag, boolean createParent, short
long
boolean logRetryCache)throws IOException
//檢查冗余策略:副本或者糾刪碼
boolean shouldReplicate = flag.contains(CreateFlag.SHOULD_REPLICATE);
//文件寫入鎖
writeLock();
//根據(jù)文件目錄字符串實(shí)例化目錄結(jié)構(gòu)。比如/hdfs/madashu,在hdfs里需要把目錄結(jié)構(gòu)映射成對象
iip = FSDirWriteFileOp.resolvePathForStartFile(
dir, pc, src, flag, createParent);
feInfo = FSDirEncryptionZoneOp.getFileEncryptionInfo(
dir, iip, ezInfo);
// 添加到文件目錄樹中:檢查文件是否已經(jīng)存在,是否可覆蓋,文件數(shù)量的限制,糾刪碼格式存儲,獲取糾刪碼策略,創(chuàng)建文件節(jié)點(diǎn)等。這里面出現(xiàn)了一個(gè)新的名詞:糾刪碼,Erasure Coding,EC。前面章節(jié)我們提到了默認(rèn)情況下,HDFS的數(shù)據(jù)塊都會保存三個(gè)副本。副本提供了一種簡單而健壯的冗余方式來最大化保證數(shù)據(jù)的可用性。數(shù)據(jù)的多副本同時(shí)可以盡量保證計(jì)算任務(wù)的本地化。但副本方式成本是較高的:默認(rèn)情況下三副本方式會在存儲空間或其他資源(比如寫入數(shù)據(jù)時(shí)的網(wǎng)絡(luò)帶寬)中產(chǎn)生200%的開銷。對于較少訪問的數(shù)據(jù)集(對集群的I/O影響相對不大),它們的第二個(gè)或者第三個(gè)副本會比較少訪問,但是仍會消耗相同的存儲空間。因此可以使用糾刪碼來代替多副本的方式,它使用更少的存儲卻可以保證相同級別的容錯(cuò)。在典型配置下,與三副本方式相比,EC可以將存儲成本降低約50%。但同樣他的使用也是需要一些代價(jià)的,一旦數(shù)據(jù)需要恢復(fù),他會造成2大資源的消耗:
1、網(wǎng)絡(luò)帶寬的消耗,因?yàn)閿?shù)據(jù)恢復(fù)需要去讀其他的數(shù)據(jù)塊和校驗(yàn)塊
2、進(jìn)行編碼,解碼計(jì)算需要消耗CPU資源
具體可參見https://cloud.tencent.com/developer/article/1363388
2.9、文件透明加密處理和目錄樹
目錄樹:

在2.8 的代碼中,還出現(xiàn)了目錄樹和文件加密,這一塊就不做多講了。分享兩個(gè)相關(guān)的鏈接:
《HDFS文件目錄詳解》https://blog.csdn.net/baiye_xing/article/details/76268495
《HDFS數(shù)據(jù)加密空間--Encryption zone》https://www.cnblogs.com/bianqi/p/12183761.html
2.10、HDFS客戶端寫流程總結(jié)
以上源碼才完成了文件創(chuàng)建過程,接下來還需要通過管道方式將文件寫入datanode中去,后續(xù)有機(jī)會再和大家一些學(xué)習(xí)分享。
// 創(chuàng)建文件。
FSDataOutputStream outputStream =
fileSystem.create(new Path("/hdfs/madashu/test"));
// 寫入文件內(nèi)容
outputStream.write("你好Hadoop,我是碼大叔".getBytes());
outputStream.flush();
IOUtils.closeStream(outputStream);以下文字來自于《Hadoop權(quán)威指南》一書,對HDFS客戶端寫流程進(jìn)行了總結(jié),作為本文的收尾,向大牛致敬!
1、創(chuàng)建文件
HDFS客戶端寫一個(gè)新的文件時(shí),會首先調(diào)用DistributedFileSystem.create()方法在HDFS文件系統(tǒng)中創(chuàng)建一個(gè)新的空文件。這個(gè)方法在底層會通過調(diào)用ClientProtocol.create()方法通知Namenode執(zhí)行對應(yīng)的操作,Namenode會首先在文件系統(tǒng)目錄樹中的指定路徑下添加一個(gè)新的文件,然后將創(chuàng)建新文件的操作記錄到editlog 中。完ClientProtocol.create()調(diào)用后,DistributedFileSystem.create()方法就會返回一個(gè)HdfsDataOutputStream對象,這個(gè)對象在底層包裝了一個(gè)DFSOutputStream對象,真正執(zhí)行寫數(shù)據(jù)操作的其實(shí)是DFSOutputStream對象。
2、 建立數(shù)據(jù)流管道
獲取了DFSOutputStream對象,HDFS客戶端就可以調(diào)用DFSOutputStream.write()方法來寫數(shù)據(jù)了。由于 DistributedFileSystem.create()方法只是在文件系統(tǒng)目錄樹中創(chuàng)建了一個(gè)空文件,并沒有申請任何數(shù)據(jù)塊,所以DFSOutputStream 會首先調(diào)用 ClientProtocol.addBlock()向 Namenode 申請一個(gè)新的空數(shù)據(jù)塊,addBlock()方法會返冋一個(gè)LocatedBlock對象,這個(gè)對象保存了存儲這個(gè)數(shù)據(jù)塊的所有數(shù)據(jù)節(jié)點(diǎn)的位置信息。獲得了數(shù)據(jù)流管道中所有數(shù)據(jù)節(jié)點(diǎn)的信息后,DFSOutputStream就可以建立數(shù)據(jù)流管道寫數(shù)據(jù)塊了。
3、通過數(shù)據(jù)流管道寫入數(shù)據(jù)
成功地建立數(shù)據(jù)流管道后,HDFS客戶端就可以向數(shù)據(jù)流管道寫數(shù)據(jù)了。寫入DFSOutputStream中的數(shù)據(jù)會先被緩存在數(shù)據(jù)流中,之后這些數(shù)據(jù)會被切分成一個(gè)個(gè)數(shù)據(jù)包(packet)通過數(shù)據(jù)流管道發(fā)送到所有數(shù)據(jù)節(jié)點(diǎn)。這里的每個(gè)數(shù)據(jù)包都會按照上圖所示,通過數(shù)據(jù)流管道依次寫入數(shù)據(jù)節(jié)點(diǎn)的本地存儲。每個(gè)數(shù)據(jù)包都有個(gè)確認(rèn)包,確認(rèn)包會逆序通過數(shù)據(jù)流管道回到輸出流。輸出流在確認(rèn)了所有數(shù)據(jù)節(jié)點(diǎn)已經(jīng)寫入這個(gè)數(shù)據(jù)包之后,就會從對應(yīng)的緩存隊(duì)列刪除這個(gè)數(shù)據(jù)包。當(dāng)客戶端寫滿一個(gè)數(shù)據(jù)塊之后,會調(diào)用addBlock()申請一個(gè)新的數(shù)據(jù)塊,然后循環(huán)執(zhí)行上述操作。
4、關(guān)閉輸入流并提交文件
當(dāng)HDFS客戶端完成了整個(gè)文件中所有數(shù)據(jù)塊的寫操作之后,就可以調(diào)用close()方法關(guān)閉輸出流,并調(diào)用ClientProtocol.completeO方法通知Namenode提交這個(gè)文件中的所有數(shù)據(jù)塊,也就完成了整個(gè)文件的寫入流程。
對于Datanode ,當(dāng)Datanode成功地接受一個(gè)新的數(shù)據(jù)塊時(shí),Datanode會通過
DatanodeProtocol.blockReceivedAndDeleted()方法向 Namenode 匯報(bào),Namenode 會更新內(nèi)存中的數(shù)據(jù)塊與數(shù)據(jù)節(jié)點(diǎn)的對應(yīng)關(guān)系。
本文參考:
《Hadoop權(quán)威指南》
《Hadoop 2.X HDFS源碼剖析 》
https://www.cnblogs.com/joqk/p/3963101.html
https://blog.csdn.net/baiye_xing/article/details/76268495
https://blog.csdn.net/androidlushangderen/article/details/52850349
http://blog.itpub.net/69908606/viewspace-2648472/
https://cloud.tencent.com/developer/article/1363388
往期推薦
1.我成功攻擊了Tomcat服務(wù)器,大佬們的反應(yīng)亮了
3.從千萬級數(shù)據(jù)查詢來聊一聊索引結(jié)構(gòu)和數(shù)據(jù)庫原理
4.AI學(xué)習(xí)筆記(一):人工智能與機(jī)器學(xué)習(xí)概述
5.史上最強(qiáng)的Java堆內(nèi)緩存框架,不接受反駁(附源碼)
6.SpringCloud第二代實(shí)戰(zhàn)系列(一):使用Nacos實(shí)現(xiàn)服務(wù)注冊與發(fā)現(xiàn)
幫忙點(diǎn)下右下角的“在看”唄??
架構(gòu)師,十年戎“碼”,老“叔”開花。感謝您的關(guān)注,我們一起學(xué)習(xí)交流!
