一、前言
Oceanbase分布式数据库的数据存储和传统数据库的数据存储形式存在一些差异,传统数据库在存储数据时有多个数据文件,而OceanBase分布式数据库的数据存储形式是一个OBServer节点对应一个block_file,其中一个OBServer节点上多个租户的数据共同使用这一个block_file,多租户之间的数据是如何隔离的,这无疑给这个block_file增加一层神秘的面纱。
在集群部署时,通过 datafile_size 和 datafile_disk_percentage 两个参数来设置 block_file 占用磁盘空间的大小,默认日志盘和数据盘共用的情况下占用磁盘的60%,日志盘和数据盘分别独占的情况下占用磁盘的90%,设置如此大的空间起初有一些不解,因为集群创建完成后,数据目录的磁盘空间使用率就已经是90%了,这些空间被 block_file 预占用了,带着这些疑问一起走进 block_file 看一看。
原来一直听说有宏块和微块,从来没听说过有超块这个概念,如果不是看了一些源文件还不知道它的存在,那本次就先看看这个超块是什么?
二、超块头
打开ob_super_block_struct.cpp文件,ObServerSuperBlock类有很多超块相关的函数包含超块的有效检测、重置超块、超块序列化、反序列化、构造超块头和超块主体等函数,超块分为超块头和超块主体分别记录不同的信息,构造超块头源代码如下:
int ObServerSuperBlock::construct_header()
{
int ret = OB_SUCCESS;
if (OB_UNLIKELY(!body_.is_valid())) {
ret = OB_INVALID_DATA;
LOG_WARN("super block body invalid", K(ret), K_(body));
} else {
// calculate crc of content serialized buffer
int64_t pos = 0;
int64_t body_buf_len = body_.get_serialize_size();
char *body_buf = static_cast<char *>(ob_malloc(body_buf_len, "SuperBlock"));
if (OB_ISNULL(body_buf)) {
ret = OB_ALLOCATE_MEMORY_FAILED;
LOG_WARN("fail to allocate memory for body", K(ret));
} else if (OB_FAIL(body_.serialize(body_buf, body_buf_len, pos))) {
LOG_WARN("fail to serialize super block body", K(ret));
} else {
header_.version_ = ObServerSuperBlockHeader::SERVER_SUPER_BLOCK_VERSION;
header_.magic_ = SERVER_SUPER_BLOCK_MAGIC;
header_.body_size_ = body_buf_len;
header_.body_crc_ = static_cast<int32_t>(ob_crc64(body_buf, body_buf_len));
ob_free(body_buf);
}
}
return ret;
}
这个函数主要是构造一个超级块的头部信息,主要是为了确保数据的可读和完整,其实超块头部就记录四个信息version_、magic_、body_size_和body_crc_,这四个信息分别表示:
- header_.version_ :记录超级块的头部结构版本号,比如一个集群中多个OceanBase版本(升级时),用于不同数据库版本对应 block_file 的不同解析方式。
- header_.magic_ :magic number 是一个固定的唯一标识,主要是可以快速检验这个超级块是否合法,同时在数据恢复时,也会检验 magic 是否匹配。
- header_.body_size_ :超级块主体数据的大小,通常为了完整准确的读取超块主体时使用,不多读也不少读。
- header_.body_crc_ :超块主体数据的crc校验值以验证数据的完整,通常理解为文件的MD5值是同样的道理,为了验证超块主体数据在存储或读取的过程中是否出现异常,ob_crc64 校验函数是64位的,通过 int32_t 只保留32位后,返回校验码。
三、超块主体
看完超块的头部,继续看一下超块的主体信息,源代码如下:
int ObServerSuperBlock::format_startup_super_block(
const int64_t macro_block_size, const int64_t data_file_size)
{
int ret = OB_SUCCESS;
if (macro_block_size <= 0 || data_file_size <= 0 || data_file_size < macro_block_size) {
ret = OB_INVALID_ARGUMENT;
LOG_WARN("invalid argument", K(ret), K(macro_block_size), K(data_file_size));
} else {
reset();
body_.create_timestamp_ = ObTimeUtility::current_time();
body_.modify_timestamp_ = body_.create_timestamp_;
body_.macro_block_size_ = macro_block_size;
body_.total_macro_block_count_ = data_file_size / macro_block_size;
body_.total_file_size_ = lower_align(data_file_size, macro_block_size);
body_.tenant_meta_entry_ = ObServerSuperBlock::EMPTY_LIST_ENTRY_BLOCK;
body_.replay_start_point_.file_id_ = 1;
body_.replay_start_point_.log_id_ = 1; // Due to the design of slog, the log_id_'s initial value must be 1
body_.replay_start_point_.offset_ = 0;
if (OB_FAIL(construct_header())) {
LOG_WARN("fail to construct super block header", K(ret), K_(body));
} else {
LOG_INFO("success to format super block", K(*this));
}
}
return ret;
}
这个函数主要是构造超块主体的信息,包括 block_file 的元数据信息,各变量分别标识:
- body_.create_timestamp_ :超块对应的 block_file 的创建时间。
- body_.modify_timestamp_ :超块对应的 block_file 的修改时间,block_file 文件写入新数据或元信息改变等情况会记录最后修改时间。
- body_.macro_block_size_ :定义的宏块规格大小。
- body_.total_macro_block_count_ :block_file 文件中总宏块数量,通过 data_file_size / macro_block_size 获得总的宏块数量。
- body_.total_file_size_ :block_file 文件的大小,是总宏块数的整数倍,通过lower_align(data_file_size, macro_block_size) 函数对齐。
- body_.tenant_meta_entry_ :指向多租户的元信息。
- body_.replay_start_point_.file_id_ :日志回放的起点,日志文件ID。
- body_.replay_start_point_.log_id_ :日志回放的起点,日志条目ID。
- body_.replay_start_point_.offset_ :日志回放的起点,日志偏移量。
四、验证
看了源文件,这些信息如何验证一下呢?可以通过官方自带的 ob_admin 工具 dump 一下超块看下内部信息,dump信息如下:
[root@localhost oceanbase]# ob_admin dumpsst -d super_block -f /home/myoceanbase
succ to open, filename=//ob_admin.log, fd=5, wf_fd=2
succ to open, filename=//ob_admin_rs.log, fd=6, wf_fd=2
successfully init ObBaseLogWriter
2026-02-12 15:38:06.553827|INFO|STORAGE_BLKMGR|OB_SERVER_BLOCK_MANAGER_START_BEGIN|0|500|3958||Y0-0000000000000000-0-0|start|ob_block_manager.cpp:183|"[server_start 1/18] block manager start begin."
2026-02-12 15:38:06.553979|INFO|STORAGE_BLKMGR|OB_SERVER_BLOCK_MANAGER_START_SUCCESS|0|500|3958||Y0-0000000000000000-0-0|start|ob_block_manager.cpp:218|"[server_start 2/18] block manager start success."
SuperBlock: {header:{version:1, magic:1018, body_size:91, body_crc:522719927}, body:{Type:"ObServerSuperBlockBody", create_timestamp:1749889669777602, modify_timestamp:1749889669777602, macro_block_size:2097152, total_macro_block_count:8192, total_file_size:17179869184, replay_start_point:ObLogCursor{file_id=1, log_id=1, offset=0}, tenant_meta_entry:{[ver=1,mode=0,seq=0][2nd=18446744073709551615]}, auto_inc_tenant_epoch:0, tenant_cnt:0}}
[root@localhost oceanbase]#
用JSON格式化内容如下:
SuperBlock: {
header: {
version: 1,
magic: 1018,
body_size: 91,
body_crc: 522719927
},
body: {
Type: "ObServerSuperBlockBody",
create_timestamp: 1749889669777602,
modify_timestamp: 1749889669777602,
macro_block_size: 2097152,
total_macro_block_count: 8192,
total_file_size: 17179869184,
replay_start_point: ObLogCursor
{
file_id=1,
log_id=1,
offset=0
},
tenant_meta_entry: {
[
ver=1,
mode=0,
seq=0
]
[
2nd=18446744073709551615
]
},
auto_inc_tenant_epoch: 0,
tenant_cnt: 0
}
}
通过以上学习,对 block_file 的头部 “超块” 有了一些了解,这对于以后进行问题故障的排查提供了一些新的思路。
注:源文件版本 oceanbase-4.2.1_CE_BP8 ,ob_admin dump信息为Oceanbase数据库社区版4.3.5.2,超块主体信息在新版本中新增了两个变量。