本文將通過一個演示工程來快速上手java調用HDFS的常見操作。接下來以創建文件為例,通過閱讀HDFS的源碼,一步步展開HDFS相關原理、理論知識的說明。
說明:本文檔基於最新版本Hadoop3.2.1
目錄
一、java調用HDFS的常見操作
1.1、演示環境搭建
1.2、操作HDFS
1.3、java文件操作常用方法
二、深入了解HDFS寫文件的流程和HDFS原理
2.1、Hadoop3.2.1 源碼下載及介紹
2.2、文件系統:FileSystem
2.3、HDFS體系結構:namenode、datanode、數據塊
2.4、如何訪問阿里雲OSS等文件系統
2.5、文件租約機制
2.6、RPC機制
2.7、HDFS客戶端寫流程總結
2.8、Hadoop3.x新特性:糾刪碼
2.9 文件透明加密處理和目錄樹
2.10、HDFS客戶端寫流程總結
一、java調用HDFS的常見操作
首先我們搭建一個簡單的演示工程(演示工程使用的gradle,Maven項目也同樣添加以下依賴),本次使用的是Hadoop最新的3.2.1。
1.1、演示環境搭建
新增一個普通的java工程即可,過程略,添加hdfs相關依賴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')
在實際運行過程中,可能會發現日志Jar包沖突問題,排除掉即可
exclude group:'org.slf4j',module: 'slf4j-log4j12'
1.2、操作HDFS
以創建文件為例,代碼如下。可以看到java操作hdfs就是這么簡單、絲滑,so easy!
public static void main(String[] args) throws IOException {
// 配置對象
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS", "hdfs://172.22.28.202:9000");
// HDFS文件系統的操作對象
FileSystem fileSystem = FileSystem.get(configuration);
// 創建文件。
FSDataOutputStream outputStream =
fileSystem.create(new Path("/hdfs/madashu/test"));
// 寫入文件內容
outputStream.write("你好Hadoop,我是碼大叔".getBytes());
outputStream.flush();
IOUtils.closeStream(outputStream);
}
1.3、java文件操作常用方法
參照第2步文件創建的操作,我們可以預定義好Configuration和FileSystem,然后提取出HDFSUtil的工具類出來。涉及到文件方面的操作基本只需要hadoop-common包下的FileSystem
就足夠了,一些常用方法的說明:
//文件是否存在
fileSystem.exists(new Path(fileName));
//創建目錄
fileSystem.mkdirs(new Path(directorName));
//刪除目錄或文件,第二個參數表示是否要遞歸刪除
fileSystem.delete(new Path(name), true);
//獲取當前登錄用戶在HDFS文件系統中的Home目錄
fileSystem.getHomeDirectory();
//文件重命名
fileSystem.rename(new Path(oldName), new Path(newName));
//讀取文件,返回的是FSDataInputStream
fileSystem.open(new Path(fileName));
//創建文件,第二個參數表示文件存在時是否覆蓋
fileSystem.create(new Path(fileName), false);
//從本地目錄上傳文件到HDFS
fileSystem.copyFromLocalFile(localPath, hdfsPath);
//獲取目錄下的文件信息,包含path,length,group,blocksize,permission等等
fileSystem.listStatus(new Path(directorName));
//釋放資源
fileSystem.close();
//設置HDFS資源權限,其中FsPermission可以設置user、group等
fileSystem.setPermission(new Path(resourceName), fsPermission);
//設置HDFS資源的Owner和group
fileSystem.setOwner(new Path(resourceName), ownerName, groupName);
//設置文件的副本
fileSystem.setReplication(new Path(resourceName), count);
二、深入了解HDFS寫文件的流程和HDFS原理
文件操作的方法比較多,本期我們以create方法為例,來通過閱讀源碼深入了解下hdfs寫文件的流程和原理,代碼參見1.2 。
2.1、Hadoop3.2.1 源碼下載及介紹
hadoop源碼地址:https://github.com/apache/hadoop,。
正常途徑下訪問比較慢的同學(每次寫到這句話,都滿臉的憂傷和xx)也可以通過國內的清華大學開源軟件鏡像站來下載,地址是https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/common/hadoop-3.2.1/hadoop-3.2.1-src.tar.gz
下載后我們可以看到這是一個maven工程,導入到idea等我們熟悉開發工具中即可。如果是使用VS需要編譯的小伙伴注意下,
目錄下有一個BUILDINDG.txt文件,針對比較關鍵的幾個modules做了說明。
這里面很多工程都是和打包相關的,有一個沒提到的“hadoop-cloud-storage-project”是和雲存儲相關的,比如我們熟悉的阿里雲,AWS等。這次我們需要關注的是hadoop-hdfs-project,hadoop-hdfs-common-project。
2.2、文件系統:FileSystem
代碼參見1.2,我們看到在操作hdfs之前首先需要根據配置文件獲取文件系統。
問題:
1、為什么傳入的地址是“hdfs:”開頭的
2、為什么要獲取文件操作系統
我們直接進入get方法
public static FileSystem get(URI uri, Configuration conf) throws IOException {
//獲取文件的前綴,即我們傳入的 hdfs:
String scheme = uri.getScheme();
// 為了便於閱讀,刪除掉很多代碼
// 從緩存中獲取
return CACHE.get(uri, conf);
}
那么緩存中存放了什么呢?一層層深入代碼,首先會檢查文件系統是否存在,不存在則創建文件系統,最終將文件系統存放在map中。
private static final Map<String, Class<? extends FileSystem>>
SERVICE_FILE_SYSTEMS = new HashMap<>();
public final class HdfsConstants {
/**
* URI Scheme for hdfs://namenode/ URIs.
*/
public static final String HDFS_URI_SCHEME = "hdfs";
我們再回過頭來打開FileSystem
類
public abstract class FileSystem extends Configured implements Closeable, DelegationTokenIssuer
可以看到FileSystem是一個抽象類,它有很多的子類即實現,比如DistributedFileSystem。所以這一步的操作實際是根據你輸入的前綴,通過Java中SPI機制從Serviceloder中獲取所需的文件操作系統。這里我們還很驚喜地看到AliyunOSSFileSystem。Hadoop3.x中默認支持阿里雲OSS對象存儲系統作為Hadoop兼容的文件系統。阿里雲OSS是中國雲計算廠商第一個也是目前唯一一個被Hadoop官方版本支持的雲存儲系統。這是繼Docker支持阿里雲存儲以后又一個更重大的里程碑,這也表明主流開源社區對中國技術生態的認可。假如我們要使用阿里雲的文件系統,前綴是什么呢?翻看AliyunOSSFileSystem
代碼
public String getScheme() {
return "oss";
}
比如 oss://madashu/test。同樣如果需要使用亞馬遜的文件系統,則前綴是“abfs://”
2.3、HDFS體系結構:namenode、datanode、數據塊
根據1.2實例代碼,獲取到文件操作系統后,就是創建文件了,最終我們跟蹤到如下的方法
public abstract FSDataOutputStream create(Path f,
FsPermission permission,
boolean overwrite,
int bufferSize,
short replication,
long blockSize,
Progressable progress) throws IOException;
參數說明:
- Path:存放路徑
- FsPermission:文件權限
- overwrite:當文件存在時是否覆蓋
- bufferSize:客戶端的buffer大小
- replication:文件副本數
- blockSize:塊大小
- Progressable:文件寫入的進度
這里有2個參數:replication和blockSize,在解釋之前得先了解一下HDFS的體系結構
HDFS是一個主/從(Master/Slave)體系結構的分布式系統,將一個大文件分成若干塊保存在不同服務器的多個節點中,通過聯網讓用戶感覺像是在本地一樣查看文件。HDFS集群擁有1個Namenode和n個Datanode,用戶可以通過HDFS客戶端和Namenode、Datanodes交互以訪問文件系統。
Namenode是HDFS的master節點,負責管理文件系統的命名空間,即namespace,他維護這文件系統樹及整棵樹內所有的文件和目錄。這些信息以命名空間鏡像文件和編輯日志文件兩個文件持久化保存在文件磁盤上。namenode也留着每個文件中各個塊所在的數據節點信息,但是並不永久保存塊的位置信息,這些塊的位置信息會在系統啟動時根據數據信息節點創建。
Datanode是文件系統的工作節點,它根據客戶端或namenode需要存儲並檢索數據塊,並且定期向nomenode發送所存儲的塊的列表。
Block是HDFS的最小存儲單元。默認大小:128M(HDFS 1.x中,默認64M),若文件大小不足128M,則會單獨成為一個block。實質上就是Linux相應目錄下的普通文件,名稱格式:blk_xxxxxxx。
HDFS塊為什么這么大呢?HDFS的塊比磁盤的塊大,主要是為了最小化尋址的開銷。如果塊足夠大,從磁盤傳輸數據的時間會明顯大於定位這個塊開始位置所需的時間。因而,傳輸一個由多個塊組成的大文件的時間取決於磁盤傳輸速率。如果一個1MB的文件存儲在一個128M的塊中時,文件實際只是用了1M的磁盤空間,而不是128M。
為了降低文件丟失造成的錯誤,它會為每個小文件復制多個副本(默認為三個),以此來實現多機器上的多用戶分享文件和存儲。
第一個復本會隨機選擇,但是不會選擇存儲過滿的節點。
第二個復本放在和第一個復本不同且隨機選擇的機架上。
第三個和第二個放在同一個機架上的不同節點上。
剩余的副本就完全隨機節點了。
補充1:create方法還有最后一個參數:Progressable,主要是為了便於我們知悉文件的寫入進度,使用方法如下:
FSDataOutputStream outputStream = fileSystem.create(new Path(targetDirector + File.separator + fileName), new Progressable() {
long fileCount = 0;
@Override
public void progress() {
fileCount++;
System.out.println("總進度:" + fileCount + "|" + fileSize + "|" + (fileCount / fileSize) * 100 + "%");
}
});
補充2:在Hadoop3.2中namenode的默認端口配置發生變化:從50070改為9870
2.4、如何訪問阿里雲OSS等文件系統
我們繼續往下扒代碼
@Override
public FSDataOutputStream create(final Path f, final FsPermission permission,
final EnumSet<CreateFlag> cflags, final int bufferSize,
final short replication, final long blockSize,
final Progressable progress, final ChecksumOpt checksumOpt)
throws IOException {
// 文件操作統計,比如創建、刪除、拷貝等等,以及操作次數
statistics.incrementWriteOps(1);
storageStatistics.incrementOpCounter(OpType.CREATE);
// 創建文件輸出流,采用了責任鏈的設計模式
Path absF = fixRelativePart(f);
return new FileSystemLinkResolver<FSDataOutputStream>() {
@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);
}
接下來再進入FileSystemLinkResolver
類:
1、調用doCall 內部類 DFSClient的create方法,然后將DFSOutputStream包裝FSDataOutputStream
2、如果是符號鏈接文件,則一層一層找到最底層的文件。甚至能連接到其他的文件系統的文件,比如從HDFS文件系統連接到阿里雲OSS文件系統、亞馬遜文件系統等。
2.5、文件租約機制
繼續跟蹤代碼,進入DFSClient
類
public DFSOutputStream create(String src, FsPermission permission,
EnumSet<CreateFlag> flag, boolean createParent, short replication,
long blockSize, Progressable progress, int buffersize,
ChecksumOpt checksumOpt, InetSocketAddress[] favoredNodes,
String ecPolicyName) throws IOException {
//檢查客戶端是否已經在運行了
checkOpen();
final FsPermission masked = applyUMask(permission);
LOG.debug("{}: masked={}", src, masked);
//創建文件輸出流,和Namenode進行交互
final DFSOutputStream result = DFSOutputStream.newStreamForCreate(this,
src, masked, flag, createParent, replication, blockSize, progress,
dfsClientConf.createChecksum(checksumOpt),
getFavoredNodesStr(favoredNodes), ecPolicyName);
//更新文件租約:也可以理解為token,保證不會發生寫文件沖突。
beginFileLease(result.getFileId(), result);
return result;
}
我們看到最后一個beginFileLease操作,也就是獲取文件租約。我們暫時先忽略文件創建的過程,繼續往下翻和FileLease有關的代碼:
//如果是第一次,還是設置文件租約。
stat = FSDirWriteFileOp.startFile(this, iip, permissions, holder,
clientMachine, flag, createParent, replication, blockSize, feInfo,
toRemoveBlocks, shouldReplicate, ecPolicyName, logRetryCache);
//設置文件租約的方法見FSDirWriteFileOp
fsn.leaseManager.addLease(
newNode.getFileUnderConstructionFeature().getClientName(),
newNode.getId());
FileLease:文件租約,HDFS給客戶端發放一個寫文件操作的臨時許可證,只有持有該證件者才允許操作此文件,從而保證保證數據的一致。
- 每個客戶端用戶持有一個文件租約。
- 每個文件租約內部包含有一個租約持有者信息,還有租約對應的文件Id列表,即當前租約持有者正在寫這些文件Id對應的文件。
- 每個文件租約內包含有一個最新近更新時間,最近更新時間將會決定此租約是否已過期。過期的租約會導致租約持有者無法繼續執行寫數據到文件中,除非進行租約的更新。
既然每個客戶端都有一個文件租約,那么HDFS如如何管理的呢?比如有些客戶端用戶寫某文件后未及時關閉此文件。這樣會導致租約未釋放,從而造成其他用戶無法對此文件進行寫操作。答案就是LeaseManager,運行在Active NameNode的服務中。它主要做2件事:
1、維護HDFS內部當前所有的租約,
2、定期釋放過期的租約對象。
補充:HDFS 只允許對一個已打開的文件順序寫入,或者在現有文件的末尾追加數據。
2.6、RPC機制
接下來我們的代碼將進入DFSOutputStream.newStreamForCreate()
方法
//調用namenode的文件創建方法
stat = dfsClient.namenode.create(src, masked, dfsClient.clientName,
new EnumSetWritable<>(flag), createParent, replication,
blockSize, SUPPORTED_CRYPTO_VERSIONS, ecPolicyName)
我們再次暫停一下,點擊“這里的namenode實際是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通信。可以操作目錄名稱空間,以及打開/關閉文件流等。ClientProtocol
是一個接口,它的實現類有:
我們進入NameNodeRpcServer.create()
方法
@Override
public HdfsFileStatus create(String src, FsPermission masked,
String clientName, EnumSetWritable<CreateFlag> flag,
boolean createParent, short replication, long blockSize,
CryptoProtocolVersion[] supportedVersions, String ecPolicyName)
throws IOException {
//確認namenode已啟動
checkNNStartup();
// 獲取服務端ip
String clientMachine = getClientMachine();
if (stateChangeLog.isDebugEnabled()) {
stateChangeLog.debug("*DIR* NameNode.create: file "
+src+" for "+clientName+" at "+clientMachine);
}
//檢查是否可以寫入。在生成上namenode正常也會進行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();
}
作為分布式文件系統,少不了各個節點之間的通信和交互,比如client和namenode,namenode和datanode,所以需要這樣一套RPC(Remote Procedure CallProtocol,遠程過程調用協議)框架,允許程序像調用本地方法一樣調用遠程機器上應用程序提供的服務。Hadoop RPC並沒有采用JDK自帶的RMI,據說基於Google Protocol Buffer(簡稱Protobuf)來實現的。Hadoop的RPC和通用的RPC一樣,包含通信模塊、客戶端Stub程序、服務端Stub程序、請求程序、服務程序等。
Hadoop RCP 主要提供兩個接口
//構造一個客戶端代理對象,用於向服務器發送RPC請求
public static <T>ProtocolProxy <T> getProxy/waitForProxy()
// 為某個協議實例構造一個服務器對象,用於處理客戶端發送的請求
public static Server RPC.Builder (Configuration).build()
2.7、HAState:active、standby
HdfsFileStatus status = null;
try {
PermissionStatus perm = new PermissionStatus(getRemoteUser()
.getShortUserName(), null, masked);
// 開始創建文件
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
// 報錯
public void checkOperation(final OperationCategory op)
throws StandbyException {
state.checkOperation(haContext, op);
}
在這個代碼里有一個HA狀態的檢查,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。當活動的namenode失效,備用的namenode能夠快速(幾十秒的時間)實現任務接管,因為最新的狀態存儲在內存中:包括最新的編輯日志條目和最新的數據塊映射信息。實際觀察到的失效時間略長一點,需要1分鍾左右,這是因為系統需要保守確定活動的namenode是否真的失效了。假設活動的namenode和備用的namenode都失效了(人品爆發了),管理員依舊可以聲明一個備用namenode並實現冷啟動。
實際開發踩坑
在實際開發過程中,由於配置或者啟動順序的原因,倒是會經查遇到standby的問題,甚至發現master和slave兩個NameNode的狀態均為standby。比如啟動了hdfs再啟動zookeeper 導致zookeeper的選舉機制zkfc(DFSZKFailoverController)沒有格式化 NameNode節點的自動切換機制沒有開啟 兩個NameNode都處於standby狀態(解決方案:先啟動zookeeper集群:zkServer.sh start 再啟動hdfs集群FSNamesystem)。
人工查看namenode的方法
sudo -E -u hadoop /home/hadoop/bin/hdfs haadmin -getServiceState nn1
2.8、Hadoop3.x新特性:糾刪碼
private HdfsFileStatus startFileInt(String src,
PermissionStatus permissions, String holder, String clientMachine,
EnumSet<CreateFlag> flag, boolean createParent, short replication,
long blockSize, CryptoProtocolVersion[] supportedVersions,
String ecPolicyName, boolean logRetryCache) throws IOException
//檢查冗余策略:副本或者糾刪碼
boolean shouldReplicate = flag.contains(CreateFlag.SHOULD_REPLICATE);
//文件寫入鎖
writeLock();
//根據文件目錄字符串實例化目錄結構。比如/hdfs/madashu,在hdfs里需要把目錄結構映射成對象
iip = FSDirWriteFileOp.resolvePathForStartFile(
dir, pc, src, flag, createParent);
feInfo = FSDirEncryptionZoneOp.getFileEncryptionInfo(
dir, iip, ezInfo);
// 添加到文件目錄樹中:檢查文件是否已經存在,是否可覆蓋,文件數量的限制,糾刪碼格式存儲,獲取糾刪碼策略,創建文件節點等。
這里面出現了一個新的名詞:糾刪碼,Erasure Coding,EC。前面章節我們提到了默認情況下,HDFS的數據塊都會保存三個副本。副本提供了一種簡單而健壯的冗余方式來最大化保證數據的可用性。數據的多副本同時可以盡量保證計算任務的本地化。但副本方式成本是較高的:默認情況下三副本方式會在存儲空間或其他資源(比如寫入數據時的網絡帶寬)中產生200%的開銷。對於較少訪問的數據集(對集群的I/O影響相對不大),它們的第二個或者第三個副本會比較少訪問,但是仍會消耗相同的存儲空間。因此可以使用糾刪碼來代替多副本的方式,它使用更少的存儲卻可以保證相同級別的容錯。在典型配置下,與三副本方式相比,EC可以將存儲成本降低約50%。但同樣他的使用也是需要一些代價的,一旦數據需要恢復,他會造成2大資源的消耗:
1、網絡帶寬的消耗,因為數據恢復需要去讀其他的數據塊和校驗塊
2、進行編碼,解碼計算需要消耗CPU資源
具體可參見https://cloud.tencent.com/developer/article/1363388
2.9、文件透明加密處理和目錄樹
目錄樹:
在2.8 的代碼中,還出現了目錄樹和文件加密,這一塊就不做多講了。分享兩個相關的鏈接:
《HDFS文件目錄詳解》https://blog.csdn.net/baiye_xing/article/details/76268495
《HDFS數據加密空間--Encryption zone》https://www.cnblogs.com/bianqi/p/12183761.html
2.10、HDFS客戶端寫流程總結
以上源碼才完成了文件創建過程,接下來還需要通過管道方式將文件寫入datanode中去,后續有機會再和大家一些學習分享。
// 創建文件。
FSDataOutputStream outputStream =
fileSystem.create(new Path("/hdfs/madashu/test"));
// 寫入文件內容
outputStream.write("你好Hadoop,我是碼大叔".getBytes());
outputStream.flush();
IOUtils.closeStream(outputStream);
以下文字來自於《Hadoop權威指南》一書,對HDFS客戶端寫流程進行了總結,作為本文的收尾,想大牛致敬!
1、創建文件
HDFS客戶端寫一個新的文件時,會首先調用DistributedFileSystem.create()方法在HDFS文件系統中創建一個新的空文件。這個方法在底層會通過調用ClientProtocol.create()方法通知Namenode執行對應的操作,Namenode會首先在文件系統目錄樹中的指定路徑下添加一個新的文件,然后將創建新文件的操作記錄到editlog 中。完ClientProtocol.create()調用后,DistributedFileSystem.create()方法就會返回一個HdfsDataOutputStream對象,這個對象在底層包裝了一個DFSOutputStream對象,真正執行寫數據操作的其實是DFSOutputStream對象。
2、 建立數據流管道
獲取了 DFSOutputStream對彖后,HDFS客戶端就可以調用DFSOutputStream.write()方法來寫數據了。由於 DistributedFileSystem.create()方法只是在文件系統目錄樹中創建了一個空文件,並沒有申請任何數據塊,所以DFSOutputStream 會首先調用 ClientProtocol.addBlock()向 Namenode 申請一個新的空數據塊,addBlock()方法會返冋一個LocatedBlock對象,這個對象保存了存儲這個數據塊的所有數據節點的位置信息。獲得了數據流管道中所有數據節點的信息后,DFSOutputStream就可以建立數據流管道寫數據塊了。
3、通過數據流管道寫入數據
成功地建立數據流管道后,HDFS客戶端就可以向數據流管道寫數據了。寫入DFSOutputStream中的數據會先被緩存在數據流中,之后這些數據會被切分成一個個數據包(packet)通過數據流管道發送到所有數據節點。這里的每個數據包都會按照上圖所示,通過數據流管道依次寫入數據節點的本地存儲。每個數據包都有個確認包,確認包會逆序通過數據流管道回到輸出流。輸出流在確認了所有數據節點已經寫入這個數據包之后,就會從對應的緩存隊列刪除這個數據包。當客戶端寫滿一個數據塊之后,會調用addBlock()申請一個新的數據塊,然后循環執行上述操作。
4、關閉輸入流並提交文件
當HDFS客戶端完成了整個文件中所有數據塊的寫操作之后,就可以調用close()方法關閉輸出流,並調用ClientProtocol.completeO方法通知Namenode提交這個文件中的所有數據塊,也就完成了整個文件的寫入流程。
對於Datanode ,當Datanode成功地接受一個新的數據塊時,Datanode會通過
DatanodeProtocol.blockReceivedAndDeleted()方法向 Namenode 匯報,Namenode 會更新內存中的數據塊與數據節點的對應關系。
我的個人微信公眾號:“碼大叔”,架構師,十年戎“碼”,老“叔”開花,我們一起學習交流!
本文參考:
《Hadoop權威指南》
《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