本文基于源码hadoop-3.3.0,个人理解欢迎指正。
目录
1 概述
2 FSDirectory源码
2.1 构造函数
2.2 变量
2.3 关于此类的方法
3 INode
3.1 构造函数
3.2 主要变量
3.3 *** 作权限
3.4 INodeDirectory
3.5 INodeFile
1 概述
namenode的一个重要作用就是维护集群中的文件目录树。对于hdfs的namespace的状态,Hadoop中提供了FSDirectory和FSNamesystem两个类进行管理,但是FSDirectory主要是管理内存中的文件系统,而FSNamesystem则是用于将集群的 *** 作记录到磁盘之中,并且FSNamesystem在FSDirectory的基础上添加了editlog的相关 *** 作。
2 FSDirectory源码FSDirectory在实现上自然是参考是Linux上的设计,因此这里也有INode的概念。
2.1 构造函数FSDirectory(FSNamesystem ns, Configuration conf) throws IOException { // 创建iNodeId,用于唯一标识iNode this.inodeId = new INodeId(); // 创建rootDir,即 / rootDir = createRoot(ns); // 创建map: GSet2.2 变量inodeMap = INodeMap.newInstance(rootDir); // 是否开启权限认证,dfs.permissions.enabled默认true this.isPermissionEnabled = conf.getBoolean( DFSConfigKeys.DFS_PERMISSIONS_ENABLED_KEY, DFSConfigKeys.DFS_PERMISSIONS_ENABLED_DEFAULT); // 是否递归检查子目录,dfs.permissions.ContentSummary.subAccess,默认false this.isPermissionContentSummarySubAccess = conf.getBoolean( DFSConfigKeys.DFS_PERMISSIONS_CONTENT_SUMMARY_SUBACCESS_KEY, DFSConfigKeys.DFS_PERMISSIONS_CONTENT_SUMMARY_SUBACCESS_DEFAULT); // 用户名 this.fsOwnerShortUserName = UserGroupInformation.getCurrentUser().getShortUserName(); // 超级用户组:dfs.permissions.superusergroup默认supergroup this.supergroup = conf.get( DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_KEY, DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_DEFAULT); // dfs.namenode.acls.enabled,是否开启acl认证,默认true this.aclsEnabled = conf.getBoolean( DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_KEY, DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_DEFAULT); LOG.info("ACLs enabled? " + aclsEnabled); // dfs.namenode.posix.acl.inheritance.enabled:true,子目录是否继承此目录权限 this.posixAclInheritanceEnabled = conf.getBoolean( DFSConfigKeys.DFS_NAMENODE_POSIX_ACL_INHERITANCE_ENABLED_KEY, DFSConfigKeys.DFS_NAMENODE_POSIX_ACL_INHERITANCE_ENABLED_DEFAULT); LOG.info("POSIX ACL inheritance enabled? " + posixAclInheritanceEnabled); // dfs.namenode.xattrs.enabled: true,是否支持扩展属性(可以理解未自定义属性) this.xattrsEnabled = conf.getBoolean( DFSConfigKeys.DFS_NAMENODE_XATTRS_ENABLED_KEY, DFSConfigKeys.DFS_NAMENODE_XATTRS_ENABLED_DEFAULT); LOG.info("XAttrs enabled? " + xattrsEnabled); // 扩展属性最大大小dfs.namenode.fs-limits.max-xattr-size:16384(16KB) this.xattrMaxSize = (int) conf.getLongBytes( DFSConfigKeys.DFS_NAMENODE_MAX_XATTR_SIZE_KEY, DFSConfigKeys.DFS_NAMENODE_MAX_XATTR_SIZE_DEFAULT); Preconditions.checkArgument(xattrMaxSize > 0, "The maximum size of an xattr should be > 0: (%s).", DFSConfigKeys.DFS_NAMENODE_MAX_XATTR_SIZE_KEY); Preconditions.checkArgument(xattrMaxSize <= DFSConfigKeys.DFS_NAMENODE_MAX_XATTR_SIZE_HARD_LIMIT, "The maximum size of an xattr should be <= maximum size" + " hard limit " + DFSConfigKeys.DFS_NAMENODE_MAX_XATTR_SIZE_HARD_LIMIT + ": (%s).", DFSConfigKeys.DFS_NAMENODE_MAX_XATTR_SIZE_KEY); // dfs.namenode.accesstime.precision:默认1h, hdfs文件的访问时间,atime this.accessTimePrecision = conf.getLong( DFS_NAMENODE_ACCESSTIME_PRECISION_KEY, DFS_NAMENODE_ACCESSTIME_PRECISION_DEFAULT); // dfs.quota.by.storage.type.enabled,默认true,是否开启配置不同存储类型的quota配额 this.quotaByStorageTypeEnabled = conf.getBoolean(DFS_QUOTA_BY_STORAGETYPE_ENABLED_KEY, DFS_QUOTA_BY_STORAGETYPE_ENABLED_DEFAULT); // dfs.ls.limit : 1000 int configuredLimit = conf.getInt( DFSConfigKeys.DFS_LIST_LIMIT, DFSConfigKeys.DFS_LIST_LIMIT_DEFAULT); this.lsLimit = configuredLimit>0 ? configuredLimit : DFSConfigKeys.DFS_LIST_LIMIT_DEFAULT; // dfs.content-summary.limit: 5000 this.contentCountLimit = conf.getInt( DFSConfigKeys.DFS_CONTENT_SUMMARY_LIMIT_KEY, DFSConfigKeys.DFS_CONTENT_SUMMARY_LIMIT_DEFAULT); // 线程休眠的时间: dfs.content-summary.sleep-microsec, 500ms this.contentSleepMicroSec = conf.getLong( DFSConfigKeys.DFS_CONTENT_SUMMARY_SLEEP_MICROSEC_KEY, DFSConfigKeys.DFS_CONTENT_SUMMARY_SLEEP_MICROSEC_DEFAULT); // filesystem limits // 最大路径长度,dfs.namenode.fs-limits.max-component-length,255b this.maxComponentLength = (int) conf.getLongBytes( DFSConfigKeys.DFS_NAMENODE_MAX_COMPONENT_LENGTH_KEY, DFSConfigKeys.DFS_NAMENODE_MAX_COMPONENT_LENGTH_DEFAULT); // 最大目录树,1024 * 1024, dfs.namenode.fs-limits.max-directory-items this.maxDirItems = conf.getInt( DFSConfigKeys.DFS_NAMENODE_MAX_DIRECTORY_ITEMS_KEY, DFSConfigKeys.DFS_NAMENODE_MAX_DIRECTORY_ITEMS_DEFAULT); // 每个inode最大的扩展属性数,dfs.namenode.fs-limits.max-xattrs-per-inode,32 this.inodeXAttrsLimit = conf.getInt( DFSConfigKeys.DFS_NAMENODE_MAX_XATTRS_PER_INODE_KEY, DFSConfigKeys.DFS_NAMENODE_MAX_XATTRS_PER_INODE_DEFAULT); this.protectedDirectories = parseProtectedDirectories(conf); Preconditions.checkArgument(this.inodeXAttrsLimit >= 0, "Cannot set a negative limit on the number of xattrs per inode (%s).", DFSConfigKeys.DFS_NAMENODE_MAX_XATTRS_PER_INODE_KEY); // We need a maximum maximum because by default, PB limits message sizes // to 64MB. This means we can only store approximately 6.7 million entries // per directory, but let's use 6.4 million for some safety. final int MAX_DIR_ITEMS = 64 * 100 * 1000; Preconditions.checkArgument( maxDirItems > 0 && maxDirItems <= MAX_DIR_ITEMS, "Cannot set " + DFSConfigKeys.DFS_NAMENODE_MAX_DIRECTORY_ITEMS_KEY + " to a value less than 1 or greater than " + MAX_DIR_ITEMS); // dfs.namenode.name.cache.threshold:10,访问次数超过此阈值的频繁访问文件缓存在 FSDirectory nameCache 中。 int threshold = conf.getInt( DFSConfigKeys.DFS_NAMENODE_NAME_CACHE_THRESHOLD_KEY, DFSConfigKeys.DFS_NAMENODE_NAME_CACHE_THRESHOLD_DEFAULT); NameNode.LOG.info("Caching file names occurring more than " + threshold + " times"); nameCache = new NameCache (threshold); namesystem = ns; this.editLog = ns.getEditLog(); ezManager = new EncryptionZoneManager(this, conf); this.quotaInitThreads = conf.getInt( DFSConfigKeys.DFS_NAMENODE_QUOTA_INIT_THREADS_KEY, DFSConfigKeys.DFS_NAMENODE_QUOTA_INIT_THREADS_DEFAULT); initUsersToBypassExtProvider(conf); } private void initUsersToBypassExtProvider(Configuration conf) { // dfs.namenode.inode.attributes.provider.bypass.users // 所有 *** 作都将绕过扩展属性检查的用户主体(在安全集群中)或用户名(在不安全集群中)的列表。 // 这意味着存储在 HDFS 而不是外部提供程序中的文件属性将用于权限检查并在请求时返回。 String[] bypassUsers = conf.getTrimmedStrings( DFSConfigKeys.DFS_NAMENODE_INODE_ATTRIBUTES_PROVIDER_BYPASS_USERS_KEY, DFSConfigKeys.DFS_NAMENODE_INODE_ATTRIBUTES_PROVIDER_BYPASS_USERS_DEFAULT); for(int i = 0; i < bypassUsers.length; i++) { String tmp = bypassUsers[i].trim(); if (!tmp.isEmpty()) { if (usersToBypassExtAttrProvider == null) { usersToBypassExtAttrProvider = new HashSet (); } LOG.info("Add user " + tmp + " to the list that will bypass external" + " attribute provider."); usersToBypassExtAttrProvider.add(tmp); } } }
此类中比较重要的变量都在构造函数中有所涉及,这里不足更多的赘述。
- rootDir在此做一点解释,这个变量是INodeDirectory类型,在这里直接初始化为"/",这是整个文件系统的根目录。
- inodeMap: 记录根目录下所有的INode,并维护INodeId ->INode的映射关系。
FSDirectory类中的方法相当多, 主要是封装了对文件系统目录树的 *** 作, 例如增、删、 改、 查等。 基本上ClientProtocol中的方法, 都能在FSDirectory中找到对应的方法。
3 INode在HDFS中,无论是目录还是文件,在文件系统目录树中都被看做是一个INode节点,如果是目录,则对应的类是INodeDirectory,如果是文件,则对应的类是INodeFile;INodeDirectory类以及INodeFile类都是INode的子类。其中INodeDirectory中包含一个成员集合变量children,如果该目录下有子目录或者文件,其子目录或文件的INode引用就会被保存在children集合中。HDFS就是通过这种方式维护整个文件系统的目录结构。
3.1 构造函数public INodeDirectory(long id, byte[] name, PermissionStatus permissions, long mtime) { super(id, name, permissions, mtime, 0L); } private INodeWithAdditionalFields(INode parent, long id, byte[] name, long permission, long modificationTime, long accessTime) { super(parent); this.id = id; this.name = name; this.permission = permission; this.modificationTime = modificationTime; this.accessTime = accessTime; } INode(INode parent) { this.parent = parent; }3.2 主要变量
INode基础抽象类,其内部保存了hdfs文件和目录共有的基本属性;包括当前节点的父节点的INode对象的引用、文件、目录名、用户组、访问权限等。需要注意的是,INode类的设计采用了模板模式。INode类定义的方法多为2个,一个是final的接口方法,用于规范接口的调用;另一个是abstract抽象方法,抽象方法留给子类具体实现。INode类中只有一个字段,就是parent,表名当前INode的父目录。
而在其子类定义了以下公共属性字段对应的方法:
- userName:文件/目录所属用户名
- groupName:文件/目录所属用户组
- fsPermission:文件或者目录权限
- modificationTime:上次修改时间
- accessTime:上次访问时间
以及INode的元数据信息对应的方法:
- id:INode的id
- name:文件/目录的名称
- fullPathName:文件/目录的完整路径
- parent:文件/目录的父节点
abstract String getUserName(int snapshotId); @Override public final String getUserName() { return getUserName(Snapshot.CURRENT_STATE_ID); } abstract String getGroupName(int snapshotId); @Override public final String getGroupName() { return getGroupName(Snapshot.CURRENT_STATE_ID); } abstract FsPermission getFsPermission(int snapshotId); @Override public final FsPermission getFsPermission() { return getFsPermission(Snapshot.CURRENT_STATE_ID); } ... // 略3.3 *** 作权限
INode作为基础的抽象类,其内部的permission字段表示用户的 *** 作权限,在INode中,permission的数据类型为long长整型,其将permission的64字节分为三段,分别用于保存访问权限、用户组标识符和用户名标识符,并巧妙地利用了Java的枚举,建立长整型上的分段 *** 作,实现了上述三个文件属性的 *** 作。
enum PermissionStatusFormat implements LongBitFormat.Enum { MODE(null, 16), GROUP(MODE.BITS, 24), USER(GROUP.BITS, 24); final LongBitFormat BITS; private PermissionStatusFormat(LongBitFormat previous, int length) { BITS = new LongBitFormat(name(), previous, length, 0); } // 提取最后23个bit的user信息 static String getUser(long permission) { final int n = (int)USER.BITS.retrieve(permission); String s = SerialNumberManager.USER.getString(n); assert s != null; return s; } // 提取中间25个bit的group信息 static String getGroup(long permission) { final int n = (int)GROUP.BITS.retrieve(permission); return SerialNumberManager.GROUP.getString(n); } // 提取前16个bit的mode信息 static short getMode(long permission) { return (short)MODE.BITS.retrieve(permission); } // 将其转化为一个long型的权限信息 static long toLong(PermissionStatus ps) { long permission = 0L; final int user = SerialNumberManager.USER.getSerialNumber( ps.getUserName()); assert user != 0; permission = USER.BITS.combine(user, permission); // ideally should assert on group but inodes are created with null // group and then updated only when added to a directory. final int group = SerialNumberManager.GROUP.getSerialNumber( ps.getGroupName()); permission = GROUP.BITS.combine(group, permission); final int mode = ps.getPermission().toShort(); permission = MODE.BITS.combine(mode, permission); return permission; } // 将long值id转化为权限对象 static PermissionStatus toPermissionStatus(long id, SerialNumberManager.StringTable stringTable) { int uid = (int)USER.BITS.retrieve(id); int gid = (int)GROUP.BITS.retrieve(id); return new PermissionStatus( SerialNumberManager.USER.getString(uid, stringTable), SerialNumberManager.GROUP.getString(gid, stringTable), new FsPermission(getMode(id))); } @Override public int getLength() { return BITS.getLength(); } }
枚举PermissionStatusFormat有三个值,MODE、GROUP、USER,分别用于处理访问权限、用户组标识符和用户名标识符。上述三个枚举值创建时,都会调用PermissionStatusFormat的构造函数,构造函数需要链各个参数,分别是枚举值对应的属性在长整形permission中的偏移量和长度。
在使用INode.getUser(permission)获取用户名的时候,由于用户名标识符保存在permission的41~63位,其会使用枚举USER.retrieve(),它会把成员变量permission和掩码USER.MASK进行与运算,然后右移,获得标识符的值;然后再保存标识符和用户名对应关系的SerialNumberManager实例中进行查找,以得到字符串形式的用户名。INode.setUser()用于设置节点的用户名,它的实现也利用了PermissionStatusFormat,使用USER.combine()设置文件所有者标识符对应位。在HDFS中,用户名和用户标识的影射、用户组名和用户组标识符的影射保存在SerialNumberManager对象中。通过SerialNumberManaer,名字节点不必在INode对象中保存字符串形式的用户名和用户组名,节省了对象对内存的占用。
INode中的方法大多比较简单,提供了对成员变量的访问/设置能力。这些方法中需要额外注意的就是isRoot()判断当前节点是否是目录树的根节点,目录树的根节点是HDFS中最重要的一个目录,所有目录都由根目录衍生。如果INode的成员属性name长度为0,约定这是HDFS的根节点,INode.isRoot()返回true,否则返回false,该节点便不是HDFS的根。
3.4 INodeDirectoryINodeDirectory抽象了HDFS中的目录,目录里面保存了文件和其他子目录。在INodeDirectory实现中的体现是其成员变量children,它是一个用于保存INode的列表。INodeDirectory中的大部分方法都是在 *** 作这个列表,如创建子目录项、查询或遍历子目录项、替换子目录项等,它们的实现都比较简单。
构造方法(继承关系可参见上图):
public INodeDirectory(long id, byte[] name, PermissionStatus permissions, long mtime) { super(id, name, permissions, mtime, 0L); } public INodeDirectory(INodeDirectory other, boolean adopt, Feature... featuresToCopy) { super(other); this.children = other.children; if (adopt && this.children != null) { for (INode child : children) { child.setParent(this); } } this.features = featuresToCopy; AclFeature aclFeature = getFeature(AclFeature.class); if (aclFeature != null) { // for the de-duplication of AclFeature removeFeature(aclFeature); addFeature(AclStorage.addAclFeature(aclFeature)); } }3.5 INodeFile
在文件系统目录树中,使用INodeFile抽象成一个hdfs文件,它也是INode的子类。INodeFile中包含了两个文件特有的属性:文件信息头header和文件对应的数据块信息blocks。
- hearder
enum HeaderFormat { PREFERRED_BLOCK_SIZE(null, 48, 1), BLOCK_LAYOUT_AND_REDUNDANCY(PREFERRED_BLOCK_SIZE.BITS, HeaderFormat.LAYOUT_BIT_WIDTH + 11, 0), STORAGE_POLICY_ID(BLOCK_LAYOUT_AND_REDUNDANCY.BITS, BlockStoragePolicySuite.ID_BIT_LENGTH, 0); private final LongBitFormat BITS; ... }
- blocks
保存了文件拥有的所有数据块信息,数组元素类型是BlockInfo。BlockInfo类继承自Block类,他保存了数据块与文件、数据块与数据节点的对应关系。
类中对数据块的 *** 作也是针对blocks,如:
@InterfaceAudience.Private public abstract class BlockInfo extends Block implements LightWeightGSet.linkedElement {...} @Override public BlockInfo getLastBlock() { return blocks.length == 0 ? null: blocks[blocks.length-1]; } void addBlock(BlockInfo newblock) { Preconditions.checkArgument(newblock.isStriped() == this.isStriped()); if (this.blocks.length == 0) { this.setBlocks(new BlockInfo[]{newblock}); } else { int size = this.blocks.length; BlockInfo[] newlist = new BlockInfo[size + 1]; System.arraycopy(this.blocks, 0, newlist, 0, size); newlist[size] = newblock; this.setBlocks(newlist); } }
- INodeFile的构造函数
INodeFile(long id, byte[] name, PermissionStatus permissions, long mtime, long atime, BlockInfo[] blklist, short replication, long preferredBlockSize) { this(id, name, permissions, mtime, atime, blklist, replication, null, preferredBlockSize, (byte) 0, CONTIGUOUS); } INodeFile(long id, byte[] name, PermissionStatus permissions, long mtime, long atime, BlockInfo[] blklist, Short replication, Byte ecPolicyID, long preferredBlockSize, byte storagePolicyID, BlockType blockType) { super(id, name, permissions, mtime, atime); final long layoutRedundancy = HeaderFormat.getBlockLayoutRedundancy( blockType, replication, ecPolicyID); header = HeaderFormat.toLong(preferredBlockSize, layoutRedundancy, storagePolicyID); if (blklist != null && blklist.length > 0) { for (BlockInfo b : blklist) { Preconditions.checkArgument(b.getBlockType() == blockType); } } setBlocks(blklist); } public INodeFile(INodeFile that) { super(that); this.header = that.header; this.features = that.features; setBlocks(that.blocks); } public INodeFile(INodeFile that, FileDiffList diffs) { this(that); Preconditions.checkArgument(!that.isWithSnapshot()); this.addSnapshotFeature(diffs); }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)