常用的rm和rmr 命令有什么区别,怎么实现的?然后Trash是啥,通过1.0.3的代码研究一下。
elif [ "$COMMAND" = "fs" ] ; then
CLASS=org.apache.hadoop.fs.FsShell
HADOOP_OPTS="$HADOOP_OPTS $HADOOP_CLIENT_OPTS"
elif [ "$COMMAND" = "dfs" ] ; then
CLASS=org.apache.hadoop.fs.FsShell
HADOOP_OPTS="$HADOOP_OPTS $HADOOP_CLIENT_OPTS"
} else if ("-rm".equals(cmd)) {
exitCode = doall(cmd, argv, i);
} else if ("-rmr".equals(cmd)) {
exitCode = doall(cmd, argv, i);
rm和rmr都进入到doall方法:
} else if ("-rm".equals(cmd)) {
delete(argv[i], false, rmSkipTrash);
} else if ("-rmr".equals(cmd)) {
delete(argv[i], true, rmSkipTrash);
void delete(String srcf, final boolean recursive, final boolean skipTrash)
throws IOException {
Path srcPattern = new Path(srcf);
new DelayedExceptionThrowing() {
@Override
void process(Path p, FileSystem srcFs) throws IOException {
delete(p, srcFs, recursive, skipTrash);
}
}.globAndProcess(srcPattern, srcPattern.getFileSystem(getConf()));
}
看到没有,中间的参数是recursive,这里搞了一个抽象类DelayedExceptionThrowing,干啥用的先不管,看名字就是异常检测之类的,往后看或者不看也知道肯定要回调process方法。
执行delete,这还是在客户端本地的代码先进行一些基本的合理性检查,比如如果你要删除的是目录,但是使用了rm命令,那肯定是不行;如果这个文件不存在也没法给你删,会抛出异常。
然后看你是不是选择了跳过trash,跳过trash给你直接调用hdfs的删除方法,默认应该都是使用Trash.
if(!skipTrash) {
try {
Trash trashTmp = new Trash(srcFs, getConf());
if (trashTmp.moveToTrash(src)) {
System.out.println("Moved to trash: " + src);
return;
}
} catch (IOException e) {
Exception cause = (Exception) e.getCause();
String msg = "";
if(cause != null) {
msg = cause.getLocalizedMessage();
}
System.err.println("Problem with Trash." + msg +". Consider using -skipTrash option");
throw e;
}
}
看上面,先搞一个trash对象出来,然后执行moveToTrash方法,后边一堆catch,不管了。这个trash咋工作的,下一篇再研究,看看跳过trash的操作:
if (srcFs.delete(src, true)) {
System.out.println("Deleted " + src);
} else {
throw new IOException("Delete failed " + src);
}
分布式文件系统实现这个抽象方法,由hdfs的客户端发起rpc调用:
public boolean delete(String src, boolean recursive) throws IOException {
checkOpen();
try {
return namenode.delete(src, recursive);
} catch(RemoteException re) {
throw re.unwrapRemoteException(AccessControlException.class);
}
}
checkopen是检查客户端有木有被意外关闭,这个先不管,继续看namenode怎么执行删除操作的。
public boolean delete(String src, boolean recursive) throws IOException {
if (stateChangeLog.isDebugEnabled()) {
stateChangeLog.debug("*DIR* Namenode.delete: src=" + src
+ ", recursive=" + recursive);
}
boolean ret = namesystem.delete(src, recursive);
if (ret)
myMetrics.incrNumDeleteFileOps();
return ret;
}
namenode还是交给自己的大管家FSNamesystem去具体操作:
public boolean delete(String src, boolean recursive) throws IOException {
if ((!recursive) && (!dir.isDirEmpty(src))) {
throw new IOException(src + " is non empty");
}
boolean status = deleteInternal(src, true);
getEditLog().logSync();
if (status && auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"delete", src, null, null);
}
return status;
}
看到没有,调用deleteInternal方法继续执行具体的删除操作,删完之后将edit更新,同时如果开启了审计日志,还要记录这个删除的操作。审计日志是个好东西啊,查找所有的操作就靠它了,但是有些人不知道没有打开过它或者揉到一般日志里,单独拿出来比较好啊,扯远了。注意啊,所有的操作记录一定要在操作完成之后再记录,前后顺序不要颠倒,虽然正常情况下不会有差别,但是操作失败了,还写个屁啊。
synchronized boolean deleteInternal(String src,
boolean enforcePermission) throws IOException {
if (NameNode.stateChangeLog.isDebugEnabled()) {
NameNode.stateChangeLog.debug("DIR* NameSystem.delete: " + src);
}
if (isInSafeMode())
throw new SafeModeException("Cannot delete " + src, safeMode);
if (enforcePermission && isPermissionEnabled) {
checkPermission(src, false, null, FsAction.WRITE, null, FsAction.ALL);
}
return dir.delete(src);
}
看到没有,safemode的时候是不能执行删除操作的。
boolean delete(String src) {
if (NameNode.stateChangeLog.isDebugEnabled()) {
NameNode.stateChangeLog.debug("DIR* FSDirectory.delete: "+src);
}
waitForReady();这个是线程一致性安全性检查
long now = FSNamesystem.now();
int filesRemoved = unprotectedDelete(src, now);
if (filesRemoved <= 0) {
return false;
}
incrDeletedFileCount(filesRemoved);
fsImage.getEditLog().logDelete(src, now);
return true;
}
waitForReady是说dir这个东西准备好了,因为很多操作要dir去干,它通过一个ready标志来标明它是否准备好了,还是它在忙乎。具体干就是unprotectedDelete这个方法了
int unprotectedDelete(String src, long modificationTime) {
src = normalizePath(src);
synchronized (rootDir) {
INode[] inodes = rootDir.getExistingPathINodes(src);
//把文件涉及的inode都找到。/a/b/c/d这样的文件,就是要找到a、b、c、d这四个inode
INode targetNode = inodes[inodes.length-1];
//显然最后一个就是我们要的目标inode
if (targetNode == null) { // non-existent src
NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: "
+"failed to remove "+src+" because it does not exist");
return 0;
} else if (inodes.length == 1) { // src is the root
NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedDelete: " +
"failed to remove " + src +
" because the root is not allowed to be deleted");//看到没有想删除root是不可以的,但是删除root下的所有东西是可以的,这种事故我日也碰到过,算是灾难性的
return 0;} else {
try {
// Remove the node from the namespace
removeChild(inodes, inodes.length-1);//例如/a/b/c/d这样的文件,d可以是文件也可以是目录,怎么删掉孩子?
//其实就是找到c,让c(inodeDirectory)在他的孩子们List children中把这个inode即d对应的inode抹去,抹去前还是要用二分查找法先找到它,保证的确是它的孩子?这个有无确实的必要?
// set the parent's modification time
inodes[inodes.length-2].setModificationTime(modificationTime);
//然后让c修改自己的mtime,注意这里看到没有文件删除会影响其父inode的mtime!!这对冷数据分析是有帮助的。
//到这里为止都是namespace的操作,或者叫inode的操作,真正的数据删除操作开始了
// GC all the blocks underneath the node.
ArrayList v = new ArrayList();
int filesRemoved = targetNode.collectSubtreeBlocksAndClear(v);
//把要删除的块放到v里
namesystem.removePathAndBlocks(src, v);
//namenode的FSNamesystem开始执行删除块操作,到底怎么删除的,接着往下看。
if (NameNode.stateChangeLog.isDebugEnabled()) {
NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: "
+src+" is removed");
}
return filesRemoved;
} catch (IOException e) {
NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedDelete: " +
"failed to remove " + src + " because " + e.getMessage());
return 0;
}
}
}
}
collectSubtreeBlocksAndClear是个抽象类,inodeDirectory和InodeFile都实现这个方法,具体看一眼,先看inodeDirectory的:
int collectSubtreeBlocksAndClear(List v) {
int total = 1;
if (children == null) {
return total;
}
for (INode child : children) {//children就是这个目录用List方式保存的所有子inode
total += child.collectSubtreeBlocksAndClear(v);//很显然,这是个递归调用。
}
parent = null;
children = null;
return total;
}
再看InodeFile怎么搞的:
int collectSubtreeBlocksAndClear(List v) {
parent = null;//每个inodefile都一个目录爹
for (Block blk : blocks) {//protected BlockInfo blocks[] = null;
//blocks管理者这个文件对应的数据块信息。(数据块信息包括块id,所处dn列表,副本数以及其它信息。)
v.add(blk); } blocks = null; return 1; }
现在明白了哈collectSubtreeBlocksAndClear方法就是把你要删除的目录下边各层的所有文件(注意不是目录,因为目录没有blk嘛,只要在namespace中清理,目录就不存在了啊)对应的BlockInfo(继承自Block)拿到手。拿到手就删吧,看看到底咋删我擦:
void removePathAndBlocks(String src, List blocks) throws IOException {
leaseManager.removeLeaseWithPrefixPath(src);//去掉这些文件的租约,关于租约回头再说。
for(Block b : blocks) {
blocksMap.removeINode(b);//大管家有个blockmap,cache住hdfs上所有的块信息,peta将其分散化,namenodecache这么多blockmap内存受不鸟啊
//
corruptReplicas.removeFromCorruptReplicasMap(b);//要删除的块,它如果是坏块,一定会出现在corruptReplicas中,这时候药膳文件了,也不用显示坏块了。
addToInvalidates(b);//顾名思议,要把这些块标记为无效块,即可删除块。
}
}
看看addToIvalidates方法
private void addToInvalidates(Block b) {
for (Iterator it =
blocksMap.nodeIterator(b); it.hasNext();) {
DatanodeDescriptor node = it.next();
addToInvalidates(b, node);//要删除的块,要根据blockInfo去找它所有的dn,
}
}
void addToInvalidatesNoLog(Block b, DatanodeInfo n) {
Collection invalidateSet = recentInvalidateSets.get(n.getStorageID());
if (invalidateSet == null) {
invalidateSet = new HashSet();
recentInvalidateSets.put(n.getStorageID(), invalidateSet);
}//构建失效块的缓存,跟下边的pendingDeletionBlocksCount共同标明当前有哪些块需要删除!
if (invalidateSet.add(b)) {
pendingDeletionBlocksCount++;
}
}
// Keeps a Collection for every named machine containing
// blocks that have recently been invalidated and are thought to live
// on the machine in question.
// Mapping: StorageID -> ArrayList
//
private Map> recentInvalidateSets =
new TreeMap>();