常用的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<Inode> 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<Block> v = new ArrayList<Block>(); 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<Block> 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<Block> 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<Block> 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<DatanodeDescriptor> it = blocksMap.nodeIterator(b); it.hasNext();) { DatanodeDescriptor node = it.next(); addToInvalidates(b, node);//要删除的块,要根据blockInfo去找它所有的dn, } } void addToInvalidatesNoLog(Block b, DatanodeInfo n) { Collection<Block> invalidateSet = recentInvalidateSets.get(n.getStorageID()); if (invalidateSet == null) { invalidateSet = new HashSet<Block>(); 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<Block> // private Map<String, Collection<Block>> recentInvalidateSets = new TreeMap<String, Collection<Block>>();