一文读懂 OceanBase 数据库的启动恢复代码解析
作者简介:镜水,一个致力于无限进步的数据库学徒。
作者简介:海芊,一个致力于当网红的 OceanBase 文档工程师。个人频道:Amber loves OB
本文主要介绍 OceanBase 数据库启动时是如何将已持久化的日志和数据恢复到内存,重新形成各类信息(如租户信息、分区信息等)的内存映像,从而回到宕机前的状态。
在介绍具体的恢复流程之前,我们首先来了解一些与之相关的存储结构。
存储数据结构
MacroBlock
OceanBase 数据库将数据分为增量数据和基线数据,基线数据是几乎占满整个磁盘的一个超大文件,OceanBase 数据库以固定大小的宏块(MacroBlock,默认大小为 2 MB)为单位对磁盘上的基线数据进行管理,从而优化每日合并过程并高效利用磁盘空间,这类似于操作系统中内存的页式管理。宏块按照其在磁盘中的位置进行逻辑编号,起始编号为 0。
MacroBlockType
枚举类型标识了宏块有哪些种类:
除此之外,类似于文件系统,宏块还有一种称为 Super Block
的特殊种类。Super Block
存放了整个基线数据的关键信息,比如元数据入口点和日志回放点,固定为第 0 块宏块,通常还有若干个备份块。
除了 Super Block
以外每个宏块都有一个 ObMacroBlockCommonHeader
。
NOTE:previous_block_index_
为前向指针,通常为链式结构的元数据宏块,它表明从新到旧的一个前向关系。例如,下表所示宏块链表的写入顺序为 12345,但由于前向链接,读取时的入口点为 5,从而导致读取顺序为 54321,因此实际使用数据时,通常需要一次正向(54321)读取获取 block id
的数组后,再进行一次反向(12345)读取,从而得到正确的数据写入顺序。
在了解了数据块的基本单元 Macro Block
后,我们接着来介绍 Super Block
以及 Meta Block
。
由于历史原因,目前对于super block
以及meta block
的组织方式有新旧两个版本之分,下面我们将这两个版本分开介绍。
旧版本:
旧版本的 Super Block
和所有的 Meta Block
都在同一个大文件(如果对 observer 的执行目录有所了解的话,这个大文件可以理解为 sstable 目录下的 block_file
)上。
Super Block
Super Block
可以简单理解为所有持久化数据的普遍元数据,其中包含了各种 Meta Block
的入口块(即宏块链表第一块)以及 SLog 的回放入口点(SLog 文件中的日志经过 checkpoint 后成为 Meta Block
,这里的回放入口点即 SLog 中尚未经过 checkpoint 形成 meta 的日志偏移位置,如果将 Meta Block
看作 meta 的基线数据,那么 SLog 需要回放的日志可以理解为 meta 的增量数据),一般是前两个 macro block
。
由于 Meta Block
是以前向链表的形式连接的,每个 Meta Block
除了 ObMacroBlockCommonHeader
以外还包含一个 link header
:
MacroBlockMeta
宏块元数据保存了数据宏块中数据列排布、微块索引等重要元数据,从磁盘加载后常驻内存并在内存中维护。
所有宏块元数据(macro block meta
,也就是 item)由多个 macro block
组成,每个 block 可能有多个 item(完整的,由于一个 item 不会跨多个 block,可能存在 padding 空间),每个 item 代表了 block id
从 0 开始的每一个 macro block
的元数据,也就是说每个 macro block
都有一个 macro block meta
。
每个 item 是 ObMacroBlockMeta
结构,解析出来的每个 item 在内存里存放在一个类似于 hash table 的结构里,通过 block id 进行 hash,解析代码见 ObMacroMetaBlockReader::parse
。
TableMgrMeta
表管理元数据保存了 SSTable 的相关元信息。
TableMgrMeta
由多个macro block
组成,可能每个 block 有多个 item(完整的,这种情况下 block 可能存在 padding 空间),也可能多个 block 组成多个 item(比如,3 个 block 组成 4 个 item,那么前两个 block 的 linked_header.meta_data_count_
为 0,最后一个 block 的 linked_header.meta_data_count_
为 4,第一个 block 的 linked_header.user_data2_
代表了这 4 个 item 的总序列化大小,第二和第三个 bloc k 的 linked_header.user_data2_
则为 0),而一次对多个 item 的读取(一个 block,或者多个 block)实际对应着一条 log 记录,是同一个 log_seq_num
。
每个 item 是 ObOldSSTable
结构,包含了一个 sstable 的信息,解析代码见 ObTableMgrMetaBlockReader::parse
。解析一条 log 中的多个 item 用的是 ObTableMgr::load_sstable
。
每个 ObOldSSTable
有一个 table_key
:
PartitionMeta
分区元数据由多个 macro block
组成,可能每个 block 有多个 item(完整的),也可能多个 block 组成多个 item(组成方式和 TableMgrMeta
相同),一次对多个 item 的读取同样对应着同一条 log 记录,解析一条 log 中的多个 item 用的是 ObPartitionMetaRedoModule::load_partition
。
每个 item 是 ObPartitionGroup
结构,包含了 partition 的全部基线信息,包括 sstable 信息。
解析代码见 ObPartitionMetaBlockReader::parse
,解析后每个 partition 的 sstable 会存放在其自身的 ObPGStorage pg_storage_.sstable_mgr_.table_map_
。
NOTE:PartitionGroup(简称 PG)和 Partition 的概念可能会增加代码阅读复杂度,可以简单理解一个 PG 里面只有一个 Partition,只有特殊情况 PG 才会包含多个 Partition,看代码时可以简单把 PG 看作 Partition。
TenantConfigMeta
租户配置元数据由多个 macro block
组成,每个 block 可能有多个 item(完整的),每个 item 是一个容纳了所有租户配置项的数组(TenantUnits
结构)。
每个 item 在ObTenantConfigMgr::load_tenant_units
载入时,会reset
原来的配置项数组,相当于覆盖更新。
解析代码见 ObTenantConfigMetaBlockReader::parse
。
新版本:
旧版本 Super Block
和所有的 meta block
都在同一个文件上,没有为不同的 tenant
做区分。而新版本则不一样,每个 tenant
有单独的 Super Block
和 meta block
,其中每个 tenant
的 Super Block
(第二级 Super Block
)是一个 SuperBlockMeta
(第一级 meta)item,和第一级 Super Block
在同一个文件,而每个 tenant
的 meta block
在 其单独的文件中,文件 id 以及元数据的入口点保存在 tenant
自己的 Super Block
中。
简单来说,通过第一级 Super Block
能够找到第一级 meta(SuperBlockMeta
)的入口点,而通过第一级 meta 能够找到每个 tenant
对应的单独文件以及对应的二级 Super Block
,然后通过二级 Super Block
上的入口点能够得到每个 tenant
的单独文件 id 以及文件上的二级 meta block
。
Super Block
这是第一级 Super Block
,针对所有的 tenant
。
Meta Block
除了 ObMacroBlockCommonHeader
以外还包含一个 link header
:
新版本的每个 meta item 都包含一个 meta header
:
TenantConfigMeta
新版本租户配置元数据和旧版本含义一致,解析代码见ObTenantConfigMetaCheckpointReader::read_checkpoint
。
每个 item 是 TenantUnits
结构,每个 item 也是调用 ObTenantConfigMgr::load_tenant_units
载入。
SuperBlockMeta
这是第一级 meta,由多个 macro block
构成,可能每个 block 有多个 i tem(完整的),也可能多个 block 组成多个 item。
新版本和旧版本对 item 的拆分方式不太一样,旧版本的拆分方式可以见TableMgrMeta
的介绍,新版本则为拆分封装了更规范的函数(ObPGMetaItemReader::get_next_item
等),通过 item_count_
来判断一个 block 是否包含完整的 item,不再通过 linked_header.user_data2_
来判断多个 item 的总长度(该成员新版本已不存在),而是通过 item_header.size_
来 判断每个 item 的长度,如果一个 item 跨多个 block,则根据 item 的长度和每个 block 的有效数据长度(common_header.payload_size_
)来拼接得到整个 item 的有效数据。
解析代码见 ObTenantFileSuperBlockCheckpointReader::read_checkpoint
,每个 item 由ObPGMetaItemReader::get_next_item
依次获取,每个 item 是 ObTenantFileSuperBlockCheckpointEntry
结构,包含了一个 ObTenantFileInfo
,ObTenantFileInfo
包含了一个 tenant
单独文件的 id 以及第二级 Super Block
,由此可以得到每个tenant
对应的单独文件以及二级元数据的入口点。
MacroBlockMeta
宏块元数据是第二级 meta,针对的是某个 tenant
,由多个 macro block
组成,每个 block 可能有多个 item(完整的),每个 item 是 ObPGMacroBlockMetaCheckpointEntry
结构,其中包含的 meta_
不再是旧版本的 ObMacroBlockMeta
结构,而是 ObMacroBlockMetaV2
。
解析出来的所有 item 存放在一个 hashmap 里,key 是 ObMacroBlockKey
结构,解析代码见 ObPGMacroMetaCheckpointReader::read_checkpoint
。
PGMeta
分区组元数据是第二级 meta,针对的是某个 tenant
,但和旧版本的 PartitionMeta
含义基本一致,依然由多个 macro block
组成,可能每个 block 有多个 item(完整的),也可能多个 block 组成多个 item,解析代码见 ObPGMetaCheckpointReader::read_checkpoint
。
每个 item 是 ObPartitionGroup
结构,包含了 partition 的全部基线信息,包括 sstable 信息。每个 item 载入内存的过程依然是调用 ObPartitionMetaRedoModule::load_partition
。
SLog
SLog 是 meta 的增量数据,相关介绍可以阅读 SLog 结构介绍 文档,这里我们简单介绍几种具体的 SLog 日志类型。
MacroBlockMeta SLog(main_type = OB_REDO_LOG_MACROBLOCK)
该日志对应于旧版本的MacroBlockMeta
,新版本不再使用。
只有一种日志类型:CHANGE_MACRO_BLOCK_META
CHANGE_MACRO_BLOCK_META
:某个 macro block 的元数据发生了变更。每个 log entry 是ObMacroBlockMetaLogEntry
结构,包含 block_id 和ObMacroBlockMeta
,意味着整个新Macro Block
元数据都在 log 里。
TableMgr SLog(main_type = OB_REDO_LOG_TABLE_MGR)
该日志对应于旧版本的 TableMgrMeta
,新版本不再使用。
包含三种日志:REDO_LOG_CREATE_SSTABLE
、REDO_LOG_COMPELTE_SSTABLE
和 REDO_LOG_DELETE_SSTABLE
。
REDO_LOG_CREATE_SSTABLE
:不支持。
REDO_LOG_COMPELTE_SSTABLE
:增加了一个 sstable。每个 log entry 是 ObCompleteSSTableLogEntry
结构,包含了 table_key 和 ObOldSSTable
。
REDO_LOG_DELETE_SSTABLE
:删除了一个 sstable。每个 log entry 是 ObDeleteSSTableLogEntry
结构,包含了 table_key。
TenantConfigMeta Slog (main_type = OB_REDO_LOG_TENANT_CONFIG)
该日志新旧版本都使用。
只有一种日志类型:REDO_LOG_UPDATE_TENANT_CONFIG
。
REDO_LOG_UPDATE_TENANT_CONFIG
:租户配置发生变更。每个 log entry 是 ObUpdateTenantConfigLogEntry
结构,包含 TenantUnits
,replay 时会将原有 tenant_units_
重置并全部更新,意味着即使只有一个租户配置变化,log 里还是保存了所有租户的配置。
PartitionMeta SLog (main_type = OB_REDO_LOG_PARTITION)
该日志新旧版本都使用,并且旧版本的部分日志类型迁移到了该日志下。
由于日志种类比较多,这里只介绍部分日志类型,感兴趣的同学可以进一步阅读代码(ObPartitionMetaRedoModule::replay
)。
REDO_LOG_ADD_PARTITION/REDO_LOG_ADD_PARTITION_GROUP/REDO_LOG_ADD_PARTITION_TO_PG
:增加 partition 或者 pg。每个 log entry 是 ObChangePartitionLogEntry
结构,只包含了ObPartitionKey
/ObPGKey
等标识信息。
REDO_LOG_ADD_SSTABLE
:某 partition 增加了 sstable。每个 log entry 是 ObAddSSTableLogEntry
结构,包含 ObPGKey
和 ObSSTable
。replay 时会将 ObSSTable
添加到 ObPGStorage::sstable_mgr_
里的 table_map_
。
REDO_LOG_CHANGE_MACRO_META
:某个 macro block 的元数据发生了变更。该日志与旧版本的CHANGE_MACRO_BLOCK_META
类型日志一致。每个 log entry 是 ObPGMacroBlockMetaLogEntry
结构,包含 block id 和 ObMacroBlockMetaV2
。
恢复流程
启动恢复过程的调用栈如下:
主要分为三个流程:
1.加载元数据的快照点,即将分区和 sstable 的信息还原进内存
2.回放 slog,将分区和 sstable 的信息更新到最新状态
3.从分区的元信息中获取 clog 的回放位点,开始回放 clog 日志生成 memory table
其中前两步调用 ObStoreFile::read_checkpoint_and_replay_log
完成,这里只介绍这一过程,建议参照代码阅读。
ObStoreFile::read_checkpoint_and_replay_log
:
1.根据 super_block
的版本走不同的分支
2.v2 版本(旧版本),先读出 ObSuperBlockV2 super_block
,然后调用 ObServerCheckpointLogReaderV1::read_checkpoint_and_replay_log
3.v3 版本(新版本),直接调用 ObServerCheckpointLogReader::read_checkpoint_and_replay_log
ObServerCheckpointLogReaderV1::read_checkpoint_and_replay_log
:
1.首先 ObServerCheckpointLogReaderV1::read_checkpoint
读取所有的 meta block
(macro_meta
/table_mgr_meta
/partition_meta
/tenant_config_meta
),并使用对应元数据的类解析函数 parse
出相应的信息恢复到内存(比如 partition_meta
能够解析出每个分区的基础信息以及包含的 sstable 信息,将以某种结构保存到内存),然后将所有 meta block
的 block id
存到同一个数组,进而在内存维护每个 meta block 的引用状态。
2.接着 ObServerCheckpointLogReaderV1::replay_slog
恢复 SLog,首先根据 super block
中的 replay_start_point_
位置从 SLog 读取每一条 log,记录 begin 和 commit(ObStorageLogCommittedTransGetter::init
),然后 ObStorageLogReplayer::replay
进行日志回放,同样从 replay_start_point_
位置开始读 SLog,log 的 trans id 如果是已经 commited 的(上一步记录了),那么 redo 该日志,即调用该 log type 对应的 replay 函数。
ObServerCheckpointLogReader::read_checkpoint_and_replay_log
:
1.首先读出 ObServerSuperBlock super_block
(第一级 Super Block
)
2.从 super_block
获取 SLog 回放入口点 replay_start_point_
,从该位置读取每一条 log,记录 begin 和 commit(ObStorageLogCommittedTransGetter::init
)
3.ObServerCheckpointLogReader::read_tenant_file_super_block_checkpoint
读取所有 tenant
的 super block meta
(这些 super block meta
相当于第一级 meta
,第二级的 super block
,和 super_block
在同一个文件上,入口点在 super_block
上 ,也是多个 macro block
前向链接而成):首先ObPGMetaBlockReader::init
从 super block meta
入口点读取所有的super block meta
(ObPGMetaBlockReader::get_meta_blocks
),并反向预取出第一个 meta block
的数据保存在内存,接着将反向读取每一个 meta block
;然后依次解析每个 meta item,将每个tenant
和其对应的 file 以 map 的形式保存在内存(ObBaseFileMgr::replay_alloc_file
)
4.ObServerCheckpointLogReader::read_tenant_meta_checkpoint
读取所有 tenant config meta block
:首先 ObTenantConfigMetaCheckpointReader::init
从 super block meta
入口点读取所有的 super block meta
(ObPGMetaBlockReader::get_meta_blocks
),并反向预取出第一个 meta block
的数据保存在内存,接着将反向读取每一个 meta block
;然后依次解析每个 meta item ,不断重新覆盖 tenant config
(ObTenantConfigMgr::load_tenant_units
)
5.ObServerCheckpointLogReader::replay_server_slog
回放第一级 meta(tenant 的 super block meta
和tenant config meta
)的 SLog,调用 ObStorageLogReplayer::replay
进行日志回放,从replay_start_point
位置开始读 SLog,log 的 trans id 如果是已经 commit 的(第 2 步记录了),那么 redo,调用相应的 replay 函数(只会对上述两种 meta 的 SLog 触发 replay)
6.ObServerCheckpointLogReader::read_pg_meta_checkpoint
根据第 3 步得到的 map 依次回放每个tenant
的二级元数据,每个 tenant
的回放调用ObTenantFilePGMetaCheckpointReader::read_checkpoint
:首先从 tenent
的 file_info
能够得到 tenant_file_super_block_
,从而获得 macro meta
和 pg meta
的 block 入口点,然后首先通过ObPGMacroMetaCheckpointReader::read_checkpoint
读取 macro meta
并保存在一个从 tenant_mgr
得到的 replay_map
中,接着通过 ObPGMetaCheckpointReader::read_checkpoint
读取 pg meta,每个 item 包含一个 pg 的信息,使用 ObPartitionMetaRedoModule::load_partition
将其解析并载入到内存
7.ObServerCheckpointLogReader::replay_other_slog
回放第二级 meta(每个 tenant 的 macro meta
以及 pg meta
)的 SLog,调用 ObStorageLogReplayer::replay
进行日志回放,从replay_start_point
位置开始读 SLog,log 的 trans id 如果是已经 commit 的(第 2 步记录了),那么 redo,调用相应的 replay 函数(只会对上述两种 meta 的 SLog 触发 replay)
8.最后 ObServerCheckpointLogReader::set_meta_block_list
将所有meta block
的 block id
存到同一个数组,进而在内存维护每个 meta block 的引用状态。
回顾
在了解了宏块、各种宏块类型、SLog 以及从 Meta 和 SLog 恢复内存数据的过程后,将会更容易理解整个 OBServer 的存储结构。整个存储结构从上到下大致可以分为这样几层:
如果您有任何疑问,可以通过以下方式与我们进行交流:
微信群:扫码添加小助手,将拉你进群哟~
钉钉群:33254054
厚脸皮地来求个 star
我们想让 Github 上优质的开源项目被更多人看到。
文档都是我们精心整理。如果有帮助的话求个 star(◕ᴗ◕✿),鼓励鼓励我们哟!
也欢迎大家给我们提 issue,请点击 这里。运营小姐姐在此跪谢️️ ❥(^_-)
欢迎大家一起参与社区贡献,指南请参考看 这里
社区答疑:请点击 这里
版权声明: 本文为 InfoQ 作者【OceanBase 开源社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/dab4c9c2f2ab966a1a682467e】。文章转载请联系作者。
评论