第8章 Namenode
Namenode主要功能包括:
1) 文件目录树管理,文件/目录元数据管理,文件索引块管理(第一关系,保存在磁盘命名空间镜像和编辑日志中)
2) 数据块与数据节点的映射关系(第二关系,由Datanode启动时提供)
8.1从i-node到INode
i-node(索引节点):前半部分包括文件权限,所有者标识,以及文件长度
后半部分包括数据块索引
@InterfaceAudience.Private
public interface INodeAttributes {
public static abstract class SnapshotCopyimplements INodeAttributes {
private final byte[] name; //文件/目錄名
private finallong permission;//访问权限,文件主标识符,用户组标识符 this.permission =PermissionStatusFormat.toLong(PermissionStatus permissions); PermissionStatusFormat为枚举类型MODE(0,15),GROUP(16,40),USER(41,63)
private final long modificationTime;//最后修改时间
private final long accessTime;//最后访问时间
privateXAttrFeature xAttrFeature;//相关属性
}
}
其中SerialNumberManager保存字符串形式的用户名与用户标识的映射,用户组名与用户组标识符的映射
final booleanisRoot() {//是否为根目录
return getLocalNameBytes().length == 0;
}
2.INodeDirectory和INodeDirectoryWithQuota
public classINodeDirectory extends INodeWithAdditionalFields
implements INodeDirectoryAttributes {
privateList<INode> children = null;//包含的子目录或者文件
public booleanremoveChild(final INode child) {//删除目录子项
final int i =searchChildren(child.getLocalNameBytes());
if (i < 0) {
return false;
}
final INode removed = children.remove(i);
Preconditions.checkState(removed == child);
return true;
}
INodeDirectory的子类是INodeDirectoryWithQuota允许管理员为每个目录设置配额
节点配额:限制目录下的名字数量
空间配额:限制存储在目录树种的所有文件的规模
3.INodeFile
@InterfaceAudience.Private
public classINodeFile extends INodeWithAdditionalFields
implements INodeFileAttributes,BlockCollection {
private longheader = 0L;//高16字节存放副本系数,低48字节存放数据块大小
private BlockInfoContiguous[] blocks;//文件对应的数据块
}
8.2命名空间镜像和编辑日志(保存Namenode的第一关系)
1.Namenode的磁盘目录文件结构
命名空间镜像(FSImage)保存在配置项{dfs.name.dir}指定目录
编辑日志保存在配置项{dfs.name.edits.dir}指定目录
{dfs.name.dir}下一般有三个目录和一个文件
Bin/Hadoop fs –format namenode 格式化namenode只会产生“current”和“image”两个目录,启动一段时间后才会有“previous.checkpoint”目录,其中命名空间镜像和编辑日志保存在”current“目录下(包括fsimage,edits,fstime,VERSION)
Namenode的VERSION种的namespaceID与Datanode的VERSION中的namespaceID一致
2.FSImage和FSEditLog
FSImage管理Namenode存储空间的生存期,同时负责命名空间镜像的保存和加载,且需要和SecondaryNamenode配合完成检查
FSEditLog记录对于元数据的修改(log*()记录)
3.命名空间镜像的保存(FSImage.saveFSImage(File newFile))
Namenode保存命名空间镜像的过程:
1) 保存镜像文件的文件头(版本号,命名空间标识,文件数,当前数据块版本号)
2) saveINode2Image()保存根节点
3) saveImage()保存其他节点
4) fsNamesys.saveFilesUnderConstruction()保存构建中的文件
5) fsNamesys.saveSecretManagerState()保存安全信息
FSImage.saveImage()命名空间镜像文件中的INodeFile信息包括(命名长度,文件绝对路径,副本数,修改时间,访问时间,数据块大小,数据块数目,数据块,用户名,用户组名,文件权限)
FSImage.saveImage()命名空间镜像文件中的INodeDirectory信息包括(命名长度,文件绝对路径,0,修改时间,0,0,-1,节点配额,空间配额,用户名,用户组名,文件权限)
FSImage.saveFilesUnderConstruction()命名空间镜像文件中的INodeFileUnderConstruction信息包括(处于构建状态的文件数,文件的绝对路径,副本数,修改时间,数据块大小,数据块数,数据块,用户名,用户组名,文件权限,客户端名称,客户端所在的主机)
命名空间镜像包括文件头部,目录树信息,租约以及安全管理
4.编辑日志数据保存
EditLogOutputStream抽象日志数据输出流(edits日志文件记录有关Namenode元数据修改的操作)
@InterfaceAudience.Private
@InterfaceStability.Evolving
public abstract class EditLogOutputStream implements Closeable {
// these are statistics counters
private long numSync; // number of sync(s) to disk
private longtotalTimeSync; // total time to sync
abstract public void write(FSEditLogOp op) throws IOException;
}
@InterfaceAudience.Private
@InterfaceStability.Unstable
publicabstract class FSEditLogOp {
public final FSEditLogOpCodes opCode;//操作码
long txid;
byte[] rpcClientId;
int rpcCallId;
}
@InterfaceAudience.Private
publicclass EditLogFileOutputStream extends EditLogOutputStream {
private static finalLog LOG = LogFactory.getLog(EditLogFileOutputStream.class);
public static final intMIN_PREALLOCATION_LENGTH = 1024 * 1024;
private File file;//待写入日志文件
private FileOutputStream fp; // file streamfor storing edit logs
private FileChannel fc; // channel of thefile stream for sync
private EditsDoubleBuffer doubleBuf;//双缓冲区包括bufCurrent(日志写入缓冲区),bufReady(写文件缓冲区)
static final ByteBuffer fill =ByteBuffer.allocateDirect(MIN_PREALLOCATION_LENGTH);
private boolean shouldSyncWritesAndSkipFsync= false;
private static booleanshouldSkipFsyncForTests = false;
static {
fill.position(0);
for (int i = 0; i < fill.capacity();i++) {
fill.put(FSEditLogOpCodes.OP_INVALID.getOpCode());
}
@Override
public void create(int layoutVersion) throwsIOException {//创建空日志文件
fc.truncate(0);、、清空
fc.position(0);
writeHeader(layoutVersion, doubleBuf.getCurrentBuf());//bufCurrent写版本
setReadyToFlush();//bufCurrent尾部写入日志文件结束标识OP_INVALID,交换俩缓冲区,bufCurrent为空,bufReady则保存要写入文件的日志内容
flush();
}
}
@Override
public void flushAndSync(boolean durable)throws IOException {
if (fp == null) {
throw new IOException("Trying to useaborted output stream");
}
if (doubleBuf.isFlushed()) {
LOG.info("Nothing to flush");
return;
}
preallocate(); // preallocate file ifnecessary
doubleBuf.flushTo(fp);//写数据到文件中
if (durable &&!shouldSkipFsyncForTests && !shouldSyncWritesAndSkipFsync) {
fc.force(false); // metadata updates notneeded
}
}
flushAndSync包含两个过程:
flush:刷新输出流并强制写出所有缓冲的输出数据(输出流->操作系统内核缓冲区)
sync:将刷新数据写入包含该文件的存储设备中(操作系统内核缓冲区->物理设备)
@InterfaceAudience.Private
publicclass EditsDoubleBuffer {
private TxnBuffer bufCurrent; // currentbuffer for writing
private TxnBuffer bufReady; // buffer readyfor flushing
private final intinitBufferSize;
FSEditLog.log*()用于向日志中写入一次Namenode第一关系修改操作
FSEditLog.logEdit()向EditLogOutputStream写入日志记录
FSEditLog.logSync()向同步日志修改
5.读取命名空间镜像和编辑日志数据
FSImage.loadFSImage()//读取命名空间镜像,添加/更新内存元数据
FSNamesystem.addToParent()//不断读取节点INodeDirectory、INodeFile、INodeDirectoryWithQuota构成目录树
FSEditLog.loadFSEdits()//加载编辑日志文件,重放并应用日志记录操作
FSNamesystem.loadFileUnderConstruction()//读取租约
FSNamesystem.loadSecretManagerState()//读取安全信息
8.3 SecondaryNameNode
@InterfaceAudience.Private
publicclass SecondaryNameNode implements Runnable,
SecondaryNameNodeInfoMXBean {
static{
HdfsConfiguration.init();
}
public static final Log LOG =
LogFactory.getLog(SecondaryNameNode.class.getName());
private final long starttime = Time.now();
private volatile long lastCheckpointTime = 0;
private URL fsName;
private CheckpointStorage checkpointImage;
private NamenodeProtocol namenode;
private Configuration conf;
private InetSocketAddress nameNodeAddr;
private volatile boolean shouldRun;
private HttpServer2 infoServer;
private Collection<URI> checkpointDirs;
private List<URI> checkpointEditsDirs;
private CheckpointConf checkpointConf;
private FSNamesystem namesystem;
private Thread checkpointThread;
private ObjectName nameNodeStatusBeanName;
private StringlegacyOivImageDir;
}
@VisibleForTesting
@SuppressWarnings("deprecated")
public boolean doCheckpoint() throwsIOException {//第二名字节点维护命名空间镜像和编辑日志
checkpointImage.ensureCurrentDirExists();
NNStorage dstStorage =checkpointImage.getStorage();
// Tell the namenode to start logging transactionsin a new edit file
// Returns a token that would be used toupload the merged image.
CheckpointSignaturesig = namenode.rollEditLog();//Namenode准备,关闭“edits的输出,并将后续的记录写到“edits.new文件
boolean loadImage = false;
boolean isFreshCheckpointer =(checkpointImage.getNamespaceID() == 0);
boolean isSameCluster =
(dstStorage.versionSupportsFederation(NameNodeLayoutVersion.FEATURES)
&&sig.isSameCluster(checkpointImage)) ||
(!dstStorage.versionSupportsFederation(NameNodeLayoutVersion.FEATURES)
&&sig.namespaceIdMatches(checkpointImage));
if (isFreshCheckpointer ||
(isSameCluster &&
!sig.storageVersionMatches(checkpointImage.getStorage()))) {
// if we're a fresh 2NN, or if we're onthe same cluster and our storage
// needs an upgrade, just take thestorage info from the server.
dstStorage.setStorageInfo(sig);
dstStorage.setClusterID(sig.getClusterID());
dstStorage.setBlockPoolID(sig.getBlockpoolID());
loadImage = true;
}
sig.validateStorageInfo(checkpointImage);
// error simulation code for junit test
CheckpointFaultInjector.getInstance().afterSecondaryCallsRollEditLog();
RemoteEditLogManifest manifest =
namenode.getEditLogManifest(sig.mostRecentCheckpointTxId + 1);
// Fetch fsimage and edits. Reload theimage if previous merge failed.
loadImage |=downloadCheckpointFiles(//从Namenode下载命名空间镜像和编辑日志
fsName, checkpointImage, sig, manifest)|
checkpointImage.hasMergeError();
try {
doMerge(sig, manifest, loadImage,checkpointImage, namesystem);//合并命名空间镜像和编辑日志
} catch (IOException ioe) {
// A merge error occurred. The in-memoryfile system state may be
// inconsistent, so the image and editsneed to be reloaded.
checkpointImage.setMergeError();
throw ioe;
}
// Clear any error since merge wassuccessful.
checkpointImage.clearMergeError();
//
// Upload the new image into the NameNode.Then tell the Namenode
// to make this new uploaded image as themost current image.
//
long txid =checkpointImage.getLastAppliedTxId();
TransferFsImage.uploadImageFromStorage(fsName,conf, dstStorage,
NameNodeFile.IMAGE, txid);//上传合并后的命名空间镜像
// error simulation code for junit test
CheckpointFaultInjector.getInstance().afterSecondaryUploadsNewImage();
LOG.warn("Checkpoint done. New ImageSize: "
+dstStorage.getFsImageName(txid).length());
if (legacyOivImageDir != null &&!legacyOivImageDir.isEmpty()) {
try {
checkpointImage.saveLegacyOIVImage(namesystem, legacyOivImageDir,
new Canceler());
} catch (IOException e) {
LOG.warn("Failed to write legacyOIV image: ", e);
}
}
return loadImage;
}
2.检查点的产生:名字节点的配合
FSImage.rollEditLog()//Namenode关闭edits输出流,并创建新文件edits.new接收之后的记录
NamenodeProtocol.rollFSImage()//Namenode更新检查点主要包括更名edits.new->edits且fsimage.ckpt(合并后的命名空间镜像文件)->fsimage,输出新的fstime和version
3.第二名字节点的启动
Bin/Hadoop fs–checkpoint secondarynamenode 启动第二节点并执行一次doCheckpoint()
Bin/Hadoop fs–geteditsize secondarynamenode 启动第二名字节点并获得名字节点当前编辑日志大小
创建SecondaryNameNodesnn=new SecondaryNameNode();
Snn.initialize();//读取配置项并创建工作时需要的CheckpointStorage对象,HTTP服务器,远程访问接口NamenodeProtocol客户端
配置项{fs.checkpoint.period}和${fs.checkpoint.size}决定了检查点执行的时间表(默认1小时产生一个检查点),如果编辑日志达到64MB,则每隔5分钟检查一次
8.4 FSDirectory的实现
Class FSDirectoryimplements FSConstants,Closeable{
FinalFSNamesystem namesystem;
FinalINodeDirectoryWithQuota rootDir;
FsImage fsImage;
Private booleanready=false;
}
FSDirectory.addToParent();//每读取命名空间镜像中的一条记录,都会调用INodeDirectory.addToParent(),若被添加的是INodeFile,则必须使用FSNamesystem.blocksMap.addNode()方法添加数据块->Datanode映射
FSDirectory.delete()过程:
1) unprotectedDelete()路径规范化
2) 调用INodeDirectory.getExistingPathINOdes()//获取从根节点到被删除节点的各级INode
3) 在父节点上调用removeChild()删除INode对象并更新父节点修改时间属性
4) INode.collectSubtreeBlocksAndClear()获得被删除节点为根的子目录树下的所有文件拥有的数据块
5) FSNamesystem.removePathAndBlocks()删除所有的数据块和可能的租约
FSDirectory.getFileInfo();//调用createFileStatus()构造并返回HdfsFileStatus
FSDirectory.setOwner()//修改文件主标识符合用户组标识符
8.5 数据块与数据节点管理
INodeFile.blocks(文件对应的数据块(BlockInfo)
BlockMap<BlockInfo,DatanodeDescriptor>
class BlocksMap {
private final int capacity;
private GSet<Block, BlockInfoContiguous> blocks;//通过GSet管理所有数据块
}
@InterfaceAudience.Private
public class BlockInfoContiguous extendsBlock
implements LightWeightGSet.LinkedElement {
public static final BlockInfoContiguous[] EMPTY_ARRAY = {};
private BlockCollection bc;
private LightWeightGSet.LinkedElement nextLinkedElement;
private Object[] triplets;//triplets[3*i]保存第i個数据节点信息DatanodeDescriptor,triplets[3*i+1]当前数据块的前一个数据块BlockInfoContiguous,triplets[3*i+2当前数据块的后一个数据块BlockInfoContiguous
}
DatanodeID->DatanodeInfo->DatanodeDescriptor(Namenode中保存的数据节点信息)
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class DatanodeDescriptor extendsDatanodeInfo {
1.数据节点状态
public boolean isAlive = false;
public final DecommissioningStatusdecommissioningStatus = new DecommissioningStatus();//只在节点撤销时使用
private EnumCounters<StorageType>currApproxBlocksScheduled
= new EnumCounters<StorageType>(StorageType.class);//估计数据节点负载(负载均衡
2.用于产生到该数据节点的Namenode指令
privatelong bandwidth;//均衡器带宽更新
private finalBlockQueue<BlockTargetPair> replicateBlocks = newBlockQueue<BlockTargetPair>();’//数据块复制
private final BlockQueue<BlockInfoContiguousUnderConstruction>recoverBlocks =
newBlockQueue<BlockInfoContiguousUnderConstruction>();//数据块恢复
private finalLightWeightHashSet<Block> invalidateBlocks = newLightWeightHashSet<Block>();//数据块删除
}
2.数据块副本状态
FSNamesystem数据块副本状态属性:
l corruptReplicas:已损坏的数据块副本(数据块扫描器,客户端,Datanode),addToCorruptReplicasMap()添加损坏数据块副本;removeFromCorruptReplicasMap()删除损坏数据块副本
l recentInvalidateSets:无效数据块副本
l excessReplicateMap:多余副本
l neededReplications:等待复制副本
l pendingReplications:等待复制副本
l leaseManager:租约管理
等待复制的数据块信息UnderReplicatedBlocks保存在priorityQueue(优先级最高的数据块最先得到复制),并根据集群数据节点负载情况,生成复制请求,并将请求放入指定节点DatanodeDescriptor的成员变量replicateBlocks中
PendingReplicationBlocks将请求信息保存在pendingReplications中
8.6 数据节点管理
HDFS提供配置项${dfs.hosts}与配置项${dfs.hosts.exclude}
${dfs.hosts}指定的dfs.hosts文件说明可以连接到Namenode的Datanode列表
${dfs.hosts.exclude}指定dfs.hosts.exclude文件说明不能连接到Namenode的Datanode列表
往include文件中添加相应记录并执行hadoop dfsadmin –refreshNodes刷新Namenode信息,最后启动数据节点
往exclude文件中添加相应记录并执行hadoop dfsadmin –refreshNodes刷新Namenode信息,最后撤销数据节点(节点被标记为正在撤销状态,数据块被复制到集群中的其他数据节点,复制完成后才会转移到“已撤销,才能关闭相应的数据节点
ClientProtocol.refreshNodes()的主要实现逻辑在FSNamesystem.refreshNodes()中
Ø 读取include文件和exclude文件
Ø 遍历Namenode管理的Datanode,若Datanode不在include文件中,则标记该Datanode为已撤销;若在include文件中,且在exclude文件中,则标记该Datanode为正在撤销(将该节点上的数据块复制到集群中的其他节点后,标记设为已撤销)
FSNamesystem.startDecommission(DatanodeDescriptornode)
Ø 调用DatanodeDescriptor.startDecommission(DatanodeDescriptor node)设置AdminStates.DECOMMISSION_INPROGRESS
Ø 调用checkDecommissionStateInternal()将该数据节点管理的数据块加入到FSNamesystem.neededReplication
FSNamesystem使用一个线程执行DecommissionManager内部类Monitor的run()方法,通过Monitor.check()定期检查处于“正在撤销的数据节点上数据块复制进度(isReplicationInProgress()根据当前处于“正常副本”的副本数和文件的期望副本数来判断数据块的复制是否结束
2.数据节点的启动
1)握手
DatanodeProtocol.versionRequest();主要逻辑是FSNamesystem.getNamespaceInfo()
2)注册
DatanodeProtocol.register();主要逻辑是FSNamesystem.registerDatanode()
1) 生成数据节点标识(DatanodeID)
2) 调用verifyNodeRegistration()可能出现的情况
Ø 数据节点未注册过
Ø 数据节点注册过,这次是重复注册
Ø 数据节点注册过,但这次注册使用了新的数据节点存储标识(storageID),表明该节点的存储空间已经被清理过,原有的数据块副本已经被删除
DatanodeDescriptornodeS=datanodeMap.get(nodeReg.getStorageID());
DatanodeDescriptornodeN=hash2DataNodeMap.getDatanodeByName(nodeReg.getName)
If(nodeN!=null&&nodeN!=nodeS){//属于第三种情况
RemoveDatanode(nodeN);
wipeDatanode(nodeN);//清理原有节点在Namenode中的保存信息
NodeN=null;
}
If(nodeS!=null){//属于第二种情况
更新节点的网络拓扑位置
更新心跳信息
}
if(nodeReg.getStorageID().equals(“”)){
NodeReg.storageID=new StorageID();//分配新的storageID
}
RemoveDatanode()//除了删除datanodeMap等对象的相应记录外,还需要更新Namenode的第二关系,由于removeStoredBlock(方法会溢出保存在BlocksMap中的一些数据块副本,而且·会改变该数据节点管理的数据块的副本状态
3)数据块上报
ClientProtocol.blockReport()的主要实现逻辑是FSNamesystem.processReport()
Ø 调用shouldNodeShutdown()判断数据节点状态是否为AdminStates.DECOMMISSIONED,若为已撤销则不予许连接Namenode
Ø DatanodeDescriptor.reportDiff(),将数据节点管理的数据块副本进行分类并添加到不同的副本状态管理对象中去
3.心跳
DataNode.offerService()//数据节点利用循环向Namenode发送心跳信息
1) 心跳信息处理
FSNamesystem.handleHeartbeat()被NameNode.sendHeartbeat()
Ø 判断该节点是否能连接到Namenode,判断该节点是否已经注册
Ø Namenode利用心跳消息的负载信息,更新整个HDFS的负载信息DatanodeDescriptor.updateHeartbeat()更新负载和心跳时间
Ø Namenode能产生Namenode指令,并通过远程调用的返回值返回
2) 心跳检查
HeartbeatCheck()执行逻辑检查,时间间隔保存在heartbeatRecheckInterval中默认5分钟·,可以通过配置项${heartbeat.recheck.interval} ,心跳检查将故障检测和故障处理分开
4.DatanodeProtocol中的其他远程方法
DatanodeProtocol.errorReport()
Ø 读写数据块时,数据节点的磁盘不可用
Ø 数据节点与名字节点握手时,系统构建版本号不一致
Ø 数据复制时,被复制的数据块不存在
5.感知网络布局
机架rack与节点node组成目录树由NetworkTopology管理,客户端读数据时,pseudoSortByDistance()近似排序以期望客户端能访问离它最近的数据节点
DNSToSwitchMapping接口用于主机->网络位置的转换(IP地址->网络位置字符串)
8.7 数据块管理
1)添加数据块副本
FSNamesystem.addStoredBlock()用于在BlockMap中添加、更新数据节点上的数据块副本block
3) 删除数据块副本
删除副本三种情况:
Ø 副本所属的文件被删除,副本也相应删除
FSDirectory.unprotectedDelete()删除目录上的一个文件
FSNamesystem.removePathAndBlocks()删除文件对应的数据块
1) 被删除的每个数据块首先从blocksMap中删除
2) 删除corruptReplicas删除可能存在的副本损坏记录
3) 调用addToInvalidates()在所有拥有副本的数据节点上删除数据块
Ø 数据块副本多余副本系数,多余的副本会被删除
FSNamesystem.processOverReplicatedBlock(0
档满足3个条件即标记为“等待删除数据块”的候选数据节点
1) 该节点信息不在excessReplicateMap中
2) 该节点不是处于“正在撤销”或者“已经撤销的数据节点
3) 该节点保存的副本已经损坏
FSNamesystem.chooseExcessReplicates()选择删除多余数据块副本
1) DatanodeDescriptor.getNetworkLocation()保存机架信息在rackMap中
2) DatanodeDescriptor.getRemaining()获得节点剩余空间
3) FSNamesystem.excessBlocks保存待删除副本
4) AddToInvalidatesNoLog()删除副本
Ø 数据块副本已经损坏,删除已损坏的副本
由客户端读文件或者数据节点上的数据块扫描器发现损坏的副本,将检测结果上报到Namenode由markBlockAsCorrupt()
InvalidateBlock(删除多余副本,删除损坏的副本CorruputRepicasMap的removeFromCorruptReplicasMap()
FSNamesystem.removeStoredBlock()删除blocksMap中相应记录
8.3 远程接口ClientProtocol
ClinetProtocol.setReplication()设置文件的数据块副本系数
ClinetProtocol.setPermission()修改文件权限
ClientProtocol.setQuota()修改目录配额
ClientProtocol.setTimes()修改文件的修改时间/访问时间
FSNamesystem.renameToInternal()修改文件名
FSDirectory.getListing()获取目录下的目录项
FSDirectory.getStats()获取文件系统的统计信息
FSDirectory.getDatanodeReport()数据节点的状态信息
FSDirectory.getPreferredBlockSize()文件的副本系数
FSDirectory.getContentSummary();文件/目录的资源占用情况
8.4 读数据使用的方法
NameNode.getBlockLocations()//获得文件数据的数据块所在位置
Ø NameNode调用getClientMachine()获取客户端地址
Ø 使用该地址对LocatedBlocks对象的数据节点列表进行排序,从而使得客户端能访问距离其最近的数据块副本
ClientProtocol.reportBadBlocks()//客户端发现读取的数据错误,将情况报告给NameNode
8.5 写数据使用的方法
1)写文件:打开文件
NameNode.create();//创建并打开空文件
NameNode.append();//打开已存在的文件
共同子方法startFileInternal();
Ø 文件系统不能处于安全模式
Ø DFSUtil.isValidName()判断文件名是否合法
Ø 创建/打开文件的目标src不能是一个目录
Ø 权限检查若为创建操作,用户在父目录下有写权限;对于追加操作,用户对该文件有写权限
Ø createParent为false,需要判断待创建文件的父目录是否存在
Ø FSNamesystem.recoverLeaseInternal(),避免多客户端同时写一个文件
Ø VerifyReplication()检查并等待打开的文件副本数是否落在有效范围内
Ø 若为追加操作,则需要保证文件存在
Ø 若为创建操作,则需要保证文件不存在
Ø 在目录树的指定位置插入一个新的INodeFileUnderConstruction节点或者将目录树的原有INodeFile转变成构建状态节点
3) 写文件:数据块提交、添加/放弃数据块
FSDirectory.addBlock()分配新数据块并添加到INodeFileUnderConstruction对象中
ClientProcotol.blockReceived()数据节点成功接收到一个数据块后,向NameNode提交数据块,更新其blocksMap
FSDirectory.removeBlock()在文件和BlocksMap中移除该数据块信息
3) 写文件:关闭文件(ClientProtocol.complete())
FSNamesystem.completeFile()和completeFileInternal()
Ø 从租约管理器中释放文件租约;
Ø 目录树种文件对应的INodeFileUnderConstruction对象替换成INodeFile对象
Ø 更新blocksMap
Ø FSDirectory.closeFile()关闭文件
8.6租约
一条租约记录包括:
Ø 客户端信息holder
Ø 租约的最后更新时间lastUpdate
Ø 客户端当前打开的所有文件paths
为追加数据打开文件,添加、放弃数据块和关闭文件时,都需要对被操作文件进行租约检查FSNamesystem.checkLease()//检查该文件是否为空/INode是目录,文件没有被打开和虽然文件被打开单输入的租约持有者不是实际的持有者
FSNamesystem.renewLease()确认NameNode不处于安全模式下,且更新Lease.lastUpdate
租约恢复
LeaseManager.Monitor每个两秒会调用LeaseManager.checkLeases()检查租约
8.7安全模式
在安全模式下,NameNode不会向DataNode发送删除或者复制指令
Hadoop dfsadmin –safemode get //查看NameNode是否处于安全模式
Hadoop dfsadmin –safemode wait //等待NameNode退出安全模式
Hadoop dfsadmin –safemode enter//NameNode不会离开安全模式
Hadoop dfsadmin –safemode leave //NameNode离开安全模式