作者:令川,一个在OceanBase专注打造稳定可靠好用存储引擎的研发同学。
OceanBase是一个单进程软件,进程名叫:observer。
本文简单介绍进程observer启动后的逻辑,里面包含进程恢复的逻辑。本文对排查进程observer启动失败原因有一定参考作用。
目录结构
在标准配置下,observer进程的执行目录结构如下所示,介绍其中几个比较关键的目录和其中的文件内容。
1.bin目录下存放是observer以及一些其他工具的二进制文件
2.etc目录下最主要的是的配置项文件,尤其是进程的一些启动参数
3.log目录下是进程运行日志,通常用于排查问题或反映系统运行的记录。
4.store目录下存放的是存储引擎关系最紧密的日志和数据文件了。它们的目录通常会是一个软连接,用于单独挂载磁盘。
日志目录有3个,分别是clog/ilog/slog。clog是数据的commit log或redo log。ilog是clog的索引,暂时可以不用了解。slog是存储引擎的redo log,是元数据的日志。三种日志的文件都是一组以单调递增序号为名字的文件。
sstable目录顾名思义,存放的就是我们的数据啦。OceanBase和其他多文件数据库不同,使用统一的2M块来组织和管理数据和元数据。因此在sstable目录下,会看到一个名为block_file的大文件。
$ob_dir ├── bin │ ├── ob_admin │ ├── obproxy │ └── observer ├── etc │ ├── io_resource.conf │ └── observer.config.bin ├── lib │ ├── libmysqlclient.so.18 │ └── libstdc++.so.6 ├── log │ ├── election.log │ ├── observer.log │ └── rootservice.log └── store ├── clog -> /data/1/clog │ └── 1 ├── ilog -> /data/1/clog │ └── 1 ├── slog -> /data/2/clog │ └── 1 └── sstable -> /data/2/clog └── block_file
OB的WAL
在介绍启动恢复流程前,简单介绍下OceanBase的WAL机制。WAL机制的原理就不重复展开了。在OceanBase里,元数据和数据的WAL日志是分开的,即上一节在目录结构下看到的slog和clog。
slog是单机引擎日志,当在节点上创建修改或是销毁分区、生成新sstable、移除旧sstable等元数据结构修改发生时,都会先写slog,而后修改内存中的元数据。后台定期会将分区和sstable的元数据以快照的形式写在2M块上,也就是slog的checkpoint机制。
clog是数据的redo log。当有数据修改发生时,请求会到达leader副本所在节点,leader会通过paxos协议先将日志同步写到其他follower的副本节点上。当有多数派节点的日志写盘成功后,数据修改成功,会再插入到内存中的memory table上。LSM Tree架构下,当memory table到达阈值后,会触发冻结和转储,数据会以sstable的形式写在block_file的宏块内。而此时的clog回放位点会推进,类似于做了checkpoint。
因为clog涉及了事务和paxos协议,需要在多个副本间协商和同步。而slog是单机引擎的元数据日志,不涉及多节点的同步和一致性问题。因此clog和slog在物理和实现上是分开的两套日志。
数据恢复流程解析
用一句话简单描述OBServer的启动恢复,就是要将store目录下的日志和数据一字不差的还原到内存中,将进程的状态恢复到宕机前的状态。在介绍具体的流程前,我们先了解下元数据和数据在磁盘上的组织格式。
快照组织格式
在内存中,observer进程是以partition-table store-sstable这三层对象来管理数据的。因为一些历史原因,目前partition对象在代码中以ObPartitonGroup
来实现;table store是分区内的memory table和sstable集合,代码实现在ObTableStore
。SSTable则对应一组数据宏块(ObSSTable
类)。
前面我们提到OceanBase在磁盘上使用2M大小的宏块来存放元数据和数据。当触发元数据快照时,每个分区都会调用各自的serialize
接口,将序列化数据写到宏块内,这些块我们称之为meta block。一个meta block只有2M,不一定能存下所有分区的元数据。因此所有meta block都会以链表的形式串联起来,直到最后一个块,我们称之为入口块ObSuperBlockMetaEntry
。入口块最终会持久化在block_file的第1和第2个宏块上,这两个宏块我们称为super block。super block上还会记录一些其他信息,例如slog的起始回放位点等等。
struct ObSuperBlockMetaEntry { blocksstable::MacroBlockId macro_block_id_; // first entry meta macro block id }; struct ServerSuperBlockContent {int64_t create_timestamp_; // create timestampint64_t modify_timestamp_; // last modified timestampint64_t macro_block_size_;int64_t total_macro_block_count_; int64_t total_file_size_; common::ObLogCursor replay_start_point_; ObSuperBlockMetaEntry super_block_meta_; ObSuperBlockMetaEntry tenant_config_meta_; }; struct ObServerSuperBlock { ObSuperBlockHeaderV2 header_; ServerSuperBlockContent content_; };
因此,当server启动时,会先从block_file的第1和第2个super block中读出上一次快照的元数据入口块,接着从这些入口块中,逐个调用partition、table store和sstable的deserialize
接口,将对象恢复到内存中去。
启动流程
observer进程的启动代码从main函数里一目了然,分为三个阶段:初始化、启动、运行。看过或熟悉OB代码的同学对于init/start/wait这三个接口不会陌生。最主要的几大组件都会有这三个接口,分别在上述的三个阶段中被调用。
ObServer& observer = ObServer::get_instance(); if (OB_FAIL(observer.init(opts, log_cfg))) { LOG_ERROR("observer init fail", K(ret)); } else if (OB_FAIL(observer.start())) { LOG_ERROR("observer start fail", K(ret)); } else if (OB_FAIL(observer.wait())) { LOG_ERROR("observer wait fail", K(ret)); }
每个组件的start流程就不展开讲了。数据和日志恢复的代码入口在ob_partition_service.cpp
文件的ObPartitionService::start()
方法内。主要流程参见下图,1)加载元数据的快照点,即将分区和sstable的信息还原进内存。2)接着,回放slog,将分区和sstable的信息更新到最新状态。3)然后,从分区的元信息中获取clog的回放位点,开始回放clog日志生成memory table。上述三步完成后,单机数据的恢复流程就告一段落了。
因此OceanBase利用WAL和快照机制相结合,保证了节点在宕机后,仍然能够从磁盘的持久化信息中心恢复出宕机前的状态,保证了数据的完整性。
代码参考
元数据快照和加载代码实现,可以参见ObServerCheckpointWriter
和ObServerCheckpointLogReader
类。
slog的重启回放代码主要在ObStorageLogReplayer
类中。
clog的重启回放代码相对会复杂一些,有兴趣的同学可以挖一下ObLogScanRunnable
这个类。
slog的读写库参见ObStorageLogReader
和ObStorageLogWriter
。
clog的读写库参见ObLogDirectReader
和ObClogWriter
。
今天我们随着OBServer的启动流程,探寻了OB的WAL和快照机制,了解了存储引擎内部多个层次对象,以及它们是如何协同工作保证数据的完整性。过程中,还有许多点并没有展开去讲,其中有不少有趣之处可再细细介绍。欢迎你也加入OceanBase,探索数据库的乐趣,一起做最先进的分布式数据库!
最后的最后:如果您有任何疑问,可以通过以下方式与我们进行交流:
微信群:扫码添加小助手,将拉你进群哟~
钉钉群:33254054