您现在的位置是:首页 >技术教程 >Fix potential FSImage corruption.网站首页技术教程

Fix potential FSImage corruption.

become__better 2023-05-30 16:00:02
简介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

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。