您现在的位置是:首页 >技术教程 >Fix potential FSImage corruption.网站首页技术教程
Fix potential FSImage corruption.
HDFS 添加 降级所需代码分析
 背景
Namenode从 hadoop 3.3.4 降级回公司版本会出现 NameNode不能启动,加载image出现遗产,
基础知识
 位域"或"位字段
将一个Interger 按二进制数位不同区域表示不同信息的方法,通常被称为"位域"或"位字段"。
在这种表示方法中,整数的每一个二进制位都被划分为不同的区域,每个区域表示不同的信息。例如,考虑以下代码:
struct Example {
 unsigned int flag1 : 1; // 第1个二进制位表示标志1
 unsigned int flag2 : 2; // 第2-3个二进制位表示标志2
 unsigned int value : 5; // 第4-8个二进制位表示值
 };
在上面的代码中,我们定义了一个结构体Example,其中包含三个位域:flag1、flag2和value。flag1占用整数的第1个二进制位,并表示一个二进制标志。flag2占用整数的第2-3个二进制位,并表示一个二进制标志。value占用整数的第4-8个二进制位,并表示一个5位的整数值。
我们可以使用位域来表示各种类型的数据,例如状态标志、位图、掩码等等。
0 1 - 0 1 0 0 - 1 0 1 0 -
 ACL
HDFS的ACL(Access Control List,访问控制列表)是用于控制HDFS文件系统中文件和目录访问权限的一种机制。通过使用ACL,可以定义更细粒度的权限控制,包括对单个文件或目录的读、写、执行等权限的控制。
public class AclEntry {
 private final AclEntryType type; //2
 private final String name; //是用户名·
 private final FsAction permission; //3
 private final AclEntryScope scope;
 }
 public enum AclEntryType {
 
 USER,
 
 GROUP,
 
 MASK, //相当于系统自身设置的
 
 OTHER;
 }
 
 public enum FsAction {
 // POSIX style
 NONE(“—”),
 EXECUTE(“–x”),
 WRITE(“-w-”),
 WRITE_EXECUTE(“-wx”),
 READ(“r–”),
 READ_EXECUTE(“r-x”),
 READ_WRITE(“rw-”),
 ALL(“rwx”);
 }
 public enum AclEntryScope {
 /**
- 这里的意思应该是这个目录或者文件,是使用自己的权限,被指定了的话,Access,还是继承上面目录的权限,default
 
*/
 ACCESS,
 
 DEFAULT;
 }
 
 public class PermissionStatus implements Writable {
 
 private String username;
 private String groupname;
 private FsPermission permission;
 }
 xattrs
HDFS xattrs 是 HDFS 对象可自定义的元数据信息,被对象的 inode 所关联。访问与设置 xattrs 依靠 HDFS SHELL 的 setfattr 与 getfattr 指令;
xattrs 以键值对的方式存储,表现形式为字符串名称与二进制数值。xattrs 名称必须以命名空间作为前缀才能生效;
HDFS xattrs 具有 5 个有效的命名空间,分别为 user,trust,system,security 和 raw,基础用户可使用 user 命名空间来设置及访问 xattrs。
 public class XAttr {
 
 public static enum NameSpace {
 USER,
 TRUSTED,
 SECURITY,
 SYSTEM,
 RAW;
 }
private final NameSpace ns;
 private final String name;
 private final byte[] value;
}
acl 、xattr相关类转换成 proto的逻辑
类 通过Format类先转换成 Interger ,然后 再变成 proto格式化。Interger只是中间状态,不是最终的存储格式。
image 存储的数据
 public enum SectionName {
 NS_INFO(“NS_INFO”),
 STRING_TABLE(“STRING_TABLE”), // here 存储的用户名和group名
 EXTENDED_ACL(“EXTENDED_ACL”),
 ERASURE_CODING(“ERASURE_CODING”),
 INODE(“INODE”),
 INODE_SUB(“INODE_SUB”),
 INODE_REFERENCE(“INODE_REFERENCE”),
 INODE_REFERENCE_SUB(“INODE_REFERENCE_SUB”),
 SNAPSHOT(“SNAPSHOT”),
 INODE_DIR(“INODE_DIR”),
 INODE_DIR_SUB(“INODE_DIR_SUB”),
 FILES_UNDERCONSTRUCTION(“FILES_UNDERCONSTRUCTION”),
 SNAPSHOT_DIFF(“SNAPSHOT_DIFF”),
 SNAPSHOT_DIFF_SUB(“SNAPSHOT_DIFF_SUB”),
 SECRET_MANAGER(“SECRET_MANAGER”),
 CACHE_MANAGER(“CACHE_MANAGER”);
 }
 private void saveInternal(FileOutputStream fout,
 FSImageCompression compression, String filePath) throws IOException {
  saveNameSystemSection(b);
  // Check for cancellation right after serializing the name system section.
  // Some unit tests, such as TestSaveNamespace#testCancelSaveNameSpace
  // depends on this behavior.
  context.checkCancelled();
 
 saveInodes(b);
 step = new Step(StepType.DELEGATION_TOKENS, filePath);
 prog.beginStep(Phase.SAVING_CHECKPOINT, step);
 saveSecretManagerSection(b);
 prog.endStep(Phase.SAVING_CHECKPOINT, step);
 
 step = new Step(StepType.CACHE_POOLS, filePath);
 prog.beginStep(Phase.SAVING_CHECKPOINT, step);
 saveCacheManagerSection(b);
 prog.endStep(Phase.SAVING_CHECKPOINT, step);
 
 saveStringTableSection(b); //
 
 // We use the underlyingOutputStream to write the header. Therefore flush
 // the buffered stream (which is potentially compressed) first.
 flushSectionOutputStream();
 
 FileSummary summary = b.build();
 saveFileSummary(underlyingOutputStream, summary);
 underlyingOutputStream.close();
 savedDigest = new MD5Hash(digester.digest());
 }
NameNode 生成image 过程
在 HDFS 中,文件系统的元数据被序列化为一个称为 FSImage 的文件。
在生成 FSImage 文件的过程中,FSImageFormatPBINode 和 FSImageFormatProtobuf 这两个类起着关键作用:
FSImageFormatPBINode:这个类主要负责将文件系统中的节点(例如,文件和目录)序列化为 Protocol Buffers(Protobuf)格式。Protobuf 是一种用于数据序列化的二进制格式,通常用于在不同系统之间交换数据。FSImageFormatPBINode 类包含了将文件系统节点转换为对应的 Protobuf 类的方法。
FSImageFormatProtobuf:这个类负责将整个文件系统的元数据序列化为 Protobuf 格式。它使用 FSImageFormatPBINode 类提供的方法将文件系统节点转换为 Protobuf 对象,并将这些对象存储在 FSImage 文件中。FSImageFormatProtobuf 类还负责加载和保存 FSImage 文件。
总结一下,生成 FSImage 文件的过程主要包括将文件系统的元数据序列化为 Protobuf 格式并将其存储在文件中。FSImageFormatPBINode 和 FSImageFormatProtobuf 这两个类起着关键作用,前者负责将文件系统节点序列化为 Protobuf 对象,后者负责将整个文件系统的元数据序列化为 Protobuf 格式并存储在 FSImage 文件中。
FSImage 对应的 fsimage.proto
NameNode 版本
改动模块
AclEntryStatusFormat
在Hadoop HDFS中,AclEntryStatusFormat是一个Java类,用于序列化和反序列化HDFS文件系统ACL(Access Control List)的状态。ACL是一种用于控制文件和目录访问权限的机制,它可以为每个文件和目录指定不同的访问控制规则。
/** An array {@link Feature}s. */
 private static final Feature[] EMPTY_FEATURE = new Feature[0];
 protected Feature[] features = EMPTY_FEATURE;
这个存储aclFeature
private static AclFeature createAclFeature(List accessEntries,
 List defaultEntries) {
 // Pre-allocate list size for the explicit entries stored in the feature,
 // which is all entries minus the 3 entries implicitly stored in the
 // permission bits.
 List featureEntries = Lists.newArrayListWithCapacity(
 (accessEntries.size() - 3) + defaultEntries.size());
 
 // For the access ACL, the feature only needs to hold the named user and
 // group entries. For a correctly sorted ACL, these will be in a
 // predictable range.
 if (!AclUtil.isMinimalAcl(accessEntries)) {
 featureEntries.addAll(
 accessEntries.subList(1, accessEntries.size() - 2));
 }
 
 // Add all default entries to the feature.
 featureEntries.addAll(defaultEntries);
 return new AclFeature(AclEntryStatusFormat.toInt(featureEntries));
 }
iNode的 aclFeature和 AclEntryStatusFormat.toInt 有关,这就是 类–》integet–》protpo中 两个版本 Integer是不一样的 proto不变 ,存储的形式不变
在新版本 Interger和 proto格式是一样的,不需要转换 。旧版本的枚举值不匹配,需要转化
FSImageFormatPBINode和FSImageFormatProtobuf
FSImageFormatPBINode和FSImageFormatProtobuf是Hadoop HDFS中用于持久化和恢复文件系统元数据的两个关键类。
FSImageFormatPBINode类用于将持久化的FSImage格式转换为Protocol Buffer格式,它实现了Writable接口,并包含了将INode节点转换为Protocol Buffer格式的方法。FSImageFormatPBINode类的主要作用是将FSImage格式的元数据转换为Protocol Buffer格式,以便在HDFS中进行存储和传输。
而FSImageFormatProtobuf类则是将Protocol Buffer格式的文件系统元数据序列化为二进制格式,并将其写入磁盘中。FSImageFormatProtobuf类实现了Writable接口,并包含了读取和写入文件系统元数据的方法。FSImageFormatProtobuf类的主要作用是将Protocol Buffer格式的元数据持久化到磁盘中,以便在HDFS重启后能够恢复文件系统的状态。
INodeWithAdditionalFields
在HDFS中,INodeWithAdditionalFields是一个接口,用于表示文件或目录的附加字段。附加字段是一些额外的元数据信息,用于存储文件或目录的属性和状态。
NameNodeLayoutVersion
在HDFS中,NameNodeLayoutVersion是一个类,用于表示当前NameNode的版本和布局。它是NameNode的一个重要组成部分,用于管理文件系统的元数据和数据块。
在HDFS中,NameNodeLayoutVersion类是一个非常重要的类,它确保了文件系统的元数据的一致性和正确性,并提供了一些重要的功能,如元数据的持久化和恢复等。
SerialNumberManager 和SerialNumberMap
这段代码定义了一个枚举类型SerialNumberManager,用于管理各种字符串表的名称到序列号的映射。
该枚举类型有四个实例:GLOBAL、USER、GROUP和XATTR。每个实例都维护了一个SerialNumberMap对象,用于将名称映射到唯一的序列号。其中,GLOBAL实例用于全局,USER和GROUP实例用于用户和组,XATTR实例用于扩展属性。
在该枚举类型的静态代码块中,计算了最大的entry bits、最大的entry number和mask bits,并为每个实例初始化了相应的SerialNumberMap对象。SerialNumberMap是一个泛型类,它维护了一个名称到序列号的映射,使用一个位图和一个计数器来实现。它还提供了get和put方法,用于将名称映射到序列号。
HDFS中的SerialNumberMap是一个用于管理字符串表名称和与之关联的序列号的类。它是由SerialNumberManager类调用和使用的,并且被设计为线程安全的。
XAttrFormat和XAttrStorage
在HDFS中,XAttrFormat和XAttrStorage两个类通常是一起使用的。首先,XAttrFormat将XAttr转换为二进制格式,然后XAttrStorage将二进制格式的XAttr存储到文件系统中。在文件系统进行读取、写入和删除操作时,XAttrStorage会相应地操作文件系统中的XAttr,并使用XAttrFormat将其转换为可读的格式,以便在HDFS中进行存储和传输。
总之,XAttrFormat和XAttrStorage是Hadoop HDFS中用于管理XAttr的两个关键类。XAttrFormat用于定义XAttr格式的接口,并提供了序列化和反序列化方法,而XAttrStorage则是用于管理XAttr存储的类,提供了读取、写入和删除XAttr的方法。这两个类通常是一起使用的,以实现对HDFS中XAttr的管理
离线图像查看器(Offline Image Viewer)
离线图像查看器(Offline Image Viewer)是一种工具,可以将hdfs fsimage文件的内容转储为人类可读的格式,以便对Hadoop集群的命名空间进行离线分析和检查。
LongBitFormat
LongBitFormat是Hadoop中的一个工具类,用于将long类型数据序列化成二进制格式,并且可以将二进制格式的数据反序列化成long类型数据。
增加代码介绍
 代码来源
https://github.com/lucasaytt/hadoop/commit/8a41edb089fbdedc5e7d9a2aeec63d126afea49f#diff-15f8fecda0a45896980cb8bdb84c68e93109be892364feba2d024619c4cb4d0f
该代码提交在hadoop中存在,却没有issue 编号,应该是社区内部人员改动。
主要 改动(官方改动和公司patch在xattrs 方面有差异)
 map存储为 proto过程
1
添加了一个 “StringTable” 类,封装了map,用于在序列化和反序列化期间保存和加载序列号映射表。在后续中,用来替代string[].
stringTable变量 由字符串数组变为了map
2 SerialNumberManager类将其变量map容量提取变为 变为枚举类型,去掉 XAttr中的map ,统一使用 枚举
3 为了2中的变化,增加了掩码,来区分 不同的类型,
总体:
新版本 统一用Stringtable作为中间状态,之前string【】 中 key是按照顺序排列的,现在stringtable中key是按照 掩码划分的。
新版本合并多个map到StringTable中
[
// returns snapshot of current values for a save.
 public static StringTable getStringTable() {
 // approximate size for capacity.
 int size = 0;
 for (final SerialNumberManager snm : values) {
 size += snm.size();
 }
 int tableMaskBits = getMaskBits();
 StringTable map = new StringTable(size, tableMaskBits);
 for (final SerialNumberManager snm : values) {
 final int mask = snm.getMask(tableMaskBits);
 for (Entry<Integer, String> entry : snm.entrySet()) {
 map.put(entry.getKey() | mask, entry.getValue());
 }
 }
 return map;
 }
stringtable的分割问题
 static {
 maxEntryBits = Integer.numberOfLeadingZeros(values.length); //这是一个Java方法,用于返回32位整数中前导零的数量
 maxEntryNumber = (1 << maxEntryBits) - 1;
 maskBits = Integer.SIZE - maxEntryBits;
 for (SerialNumberManager snm : values) {
 // account for string table mask bits.
 snm.updateLength(maxEntryBits);
 snm.serialMap = new SerialNumberMap(snm);
 FSDirectory.LOG.info(snm + " serial map: bits=" + snm.getLength() +
 " maxEntries=" + snm.serialMap.getMax());
 }
 }
values.length=4
maxEntryBits =29 (32-3) (实际存储信息的数位)
maskBits=3 (掩码在最前面作为 区分位,区分四种 map)
AclEntryStatusFormat中 枚举类型发生了变化
message AclFeatureProto {
 /**
- An ACL entry is represented by a 32-bit integer in Big Endian
 - format. The bits can be divided in four segments:
 - [0:2) || [2:26) || [26:27) || [27:29) || [29:32)
 - [0:2) – reserved for futute uses.
 - [2:26) – the name of the entry, which is an ID that points to a
 - string in the StringTableSection.
 - [26:27) – the scope of the entry (AclEntryScopeProto)
 - [27:29) – the type of the entry (AclEntryTypeProto)
 - [29:32) – the permission of the entry (FsActionProto)
 
*/
 repeated fixed32 entries = 2 [packed = true];
 }
为了将 枚举变量和 proto进行匹配,这样转换时可以直接转
去掉的枚举类型:
AclEntryStatusFormat:当使用AclEntryStatusFormat将ACL条目转换为字符串时,可以指定NAMED_ENTRY_CHECK常量,从而控制输出字符串是否包含主体的名称
RESERVED是 预留空间,不进行分析
因为 在后续的代码中 ,当没有name时, 当name=0 来代替AclEntryStatusFormat=0 name=0 ,当name =0时,我们就知道name对应string为null,不需要再添加一个标志位。
XAttrFormat中枚举类型发生变化
message XAttrCompactProto {
 /**
 *
- [0:2) – the namespace of XAttr (XAttrNamespaceProto)
 - [2:26) – the name of the entry, which is an ID that points to a
 - string in the StringTableSection.
 - [26:27) – namespace extension. Originally there were only 4 namespaces
 - so only 2 bits were needed. At that time, this bit was reserved. When a
 - 5th namespace was created (raw) this bit became used as a 3rd namespace
 - bit.
 - [27:32) – reserved for future uses.
*/
required fixed32 name = 1;
optional bytes value = 2;
} 
为了将 枚举变量和 proto进行匹配,这样转换时可以直接转
Group 和 USER变为 24
private final static long USER_GROUP_STRID_MASK = (1 << 24) - 1;
 private final static int USER_STRID_OFFSET = 40;
 private final static int GROUP_STRID_OFFSET = 16;
 public static PermissionStatus loadPermission(long id,
 final String[] stringTable) {
 short perm = (short) (id & ((1 << GROUP_STRID_OFFSET) - 1));
 int gsid = (int) ((id >> GROUP_STRID_OFFSET) & USER_GROUP_STRID_MASK);
 int usid = (int) ((id >> USER_STRID_OFFSET) & USER_GROUP_STRID_MASK);
 return new PermissionStatus(stringTable[usid], stringTable[gsid],
 new FsPermission(perm));
 }
逻辑上的错误:16+25=41, 当gropu是25时,因为USER_GROUP_STRID_MASK 是24位,第25位的值无法进行获取。
完善了 serialNumbermap 类
增加了 name ,max变量,用current来替代之前的 max,用来判断边界值的情形发生了变化,用sn>max 更专缺,sn<0 意味着已经超过Integer的最大值,可能比表的范围更大。
FSImageFormatPBINode 中的 转换方法集成到PermissionStatusFormat、 AclEntryStatusFormat 和 XAttrFormat 类中
NameNode 版本更新
可能存在问题
1 image 传输问题
做checkpoint时, standbyNameNode(新版本)传输 image到 active NameNode(旧版本),active NameNode不能重启
2 Xattr 差距问题
XattrFormat 和 Xattr storage 两个类差别较大。
方法 : 1 验证集群使用 xattr功能 ,加载image时 在这个方法中打印日志
private static XAttrFeatureProto.Builder buildXAttrs(XAttrFeature f,
 final SaverContext.DeduplicationMap stringMap) {
 XAttrFeatureProto.Builder b = XAttrFeatureProto.newBuilder();
 for (XAttr a : f.getXAttrs()) {
 XAttrCompactProto.Builder xAttrCompactBuilder = XAttrCompactProto.
 newBuilder();
 int nsOrd = a.getNameSpace().ordinal();
 Preconditions.checkArgument(nsOrd < 8, “Too many namespaces.”);
 int v = ((nsOrd & XATTR_NAMESPACE_MASK) << XATTR_NAMESPACE_OFFSET)
 | ((stringMap.getId(a.getName()) & XATTR_NAME_MASK) <<
 XATTR_NAME_OFFSET);
 v |= (((nsOrd >> 2) & XATTR_NAMESPACE_EXT_MASK) <<
 XATTR_NAMESPACE_EXT_OFFSET);
 xAttrCompactBuilder.setName(v);
 if (a.getValue() != null) {
 xAttrCompactBuilder.setValue(PBHelper.getByteString(a.getValue()));
 }
 b.addXAttrs(xAttrCompactBuilder.build());
 }
 
 return b;
 }
 
 
2 将线上image 用 测试代码启动 ,进行测试
xattr功能的开启
dfs.namenode.xattrs.enabled true Whether support for extended attributes is enabled on the NameNode.参考资料
https://xie.infoq.cn/article/dc898e0ece2c25e7c4cf112a0
            




U8W/U8W-Mini使用与常见问题解决
QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结