一文读懂 OceanBase 数据库的 SLog 日志
作者简介:镜水,一个无限进步的数据库学徒。
作者简介:海芊,一个致力于当网红的 OceanBase 文档工程师。
本文主要介绍 OceanBase 数据库的 Slog 日志,从代码层面剖析 Slog 日志的模块结构和工作机制,帮助大家深入理解 OceanBase 数据库的 Slog 日志。
基本概念
我们都知道 OceanBase 数据库的 Clog 日志类似于传统数据库的 Redo 日志,因此在分布式场景下需要多副本同步。而 Slog 不一样,Slog 可以理解为服务器的本地日志,是一台服务器上一些全局信息变更操作(如新增租户、分区创建和新增 SSTable 等)的 redo log。
一个服务器只拥有一个 Slog 写入流,也就是说同一台服务器上具有不同资源池的不同租户并不具有单独的 Slog 文件,所有租户的 Slog 写入请求最后都会汇入服务器所拥有的 Slog 文件中。
一条 Slog 日志记录的格式为:
从上图可以看出,Slog 分为三部分,其中每个部分可以理解为一个 log 块,具有递增的 log_seq 序号,保存在LogEntry
内(log 块的 header):
1. Logs,有效的 redo log 内容,可能包含多条实际的子 redo log,每条子 log 有子 log_seq(从 0 开始递增)。
2.NopLog,无意义的 log 内容,只是为了让整条记录 4k 对齐。
3.SwitchLog,只在一个 slog 文件的最后一条记录出现,包含 next file id,便于切换下一个 Slog 文件继续读取,同时也会包含 padding buffer 做对齐。
代码分析
接下来从代码层面展开分析 Slog。(由于 NopLog 和 SwtichLog 没有实际的 redo log 内容,因此这里不展开分析)
日志结构
每条 Slog 记录的 logs 部分(上图所示的第一部分 log 块)的格式如下:
ObLogEntry + n * log content
ObLogEntry
可以理解为整个 log 块的 header:
n * log content
在内存中由 ObStorageLogActiveTrans
结构进行组织管理,以序列化的形式共同存放在 log_buffer_
:
每个 log content
由一个 log header(ObBaseStorageLogHeader
结构)和实际的 log data 组成。
每次 Slog 的写入可以看做是一次事务,包括了 trans begin/n * trans/trans commit,而每个事务的子操作对应着一个 log content
(包括 begin/commit)。
写入流程
1.ObBaseStorageLogger::begin
开启一次 Slog 写事务:
(1)从ObBaseStorageLogger
的 ObStorageActiveTrans
池中取出一个元素 trans_entry 用于本次 Slog 写事务。
(2)向 trans_entry 中 append 一条 begin 日志,即写入 trans_entry 的 log_buffer_
。
2.多次调用 ObBaseStorageLogger::write_log
:
(1)正常条件下每次调用都向 trans_entry 的 log_buffer_
append 一条 log content
。
(2)当 trans_entry 的 log_buffer_
达到上限(512-3*4-4 KB)时,对 log_buffer_
里已有的 log 内容进行 flush。
(3)当 log 本身长度超出上限(512-3*4-4 KB)时,首先对 log_buffer_
里已有的 log 内容进行 flush,然后扩大 trans_entry 的 log_buffer_
,将超长 log append 到 log_buffer_
,并再次 flush,最后恢复 trans_entry 的 log_buffer_
大小。
3.ObBaseStorageLogger::commit
提交(结束)Slog 写事务:
(1)向 trans_entry 中 append 一条 begin 日志,即写入 trans_entry 的 log_buffer_
。
(2)直接对 log_buffer_
进行 flush。
flush 流程
1.ObBaseStorageLogger::flush_log
对一个 log_buffer_
已满(或超长 log)的 ObStorageActiveTrans
进行刷盘操作:
(1)调用 Slog 写盘类 ObStorageLogWriter
的 flush_log
函数进行 Slog 落盘。
(2)首先从 ObStorageLogWriter
的 ObStorageLogItem
(结构如下)池中取出一个元素 log_item
,然后使用 log_buffer_
构造 Logs 的 log 块(log_buffer_
就对应了前文的 n*log content
),并依次构造 NopLog 的 log 块以及 SwitchLog 的 log 块(如果有的话),最终构造成完整的一条 Slog 记录(最长为 32 MB)并填充到 log_item
的 buf_
。
(3)log_item
构造完成后进行写盘操作(涉及到异步操作,这里不做介绍)。
flush 流程的核心是通过写流程中得到的 n * log content
构造出一条完整的 Slog 记录,通常可以直接用 ObStorageLogItem
结构指代。
示例
上图是 ob_admin 的 slog_tool 工具所显示的某 slog 文件的部分日志内容。
1.log_seq
=1 的 redo log 共有三个 log content
,其中每个 log content
都有自身的 sub_seq
;
(1)每个 log content
的 command = 626(OB_LOG_TABLE_MGR
),表明都是与 TableMgr 相关的 log。
(2)sub_seq
=1 的子日志的 main_type
= 3(OB_REDO_LOG_TABLE_MGR
),表明是与 TableMgr 相关的 log,而 sub_type
在图中没有以值的方式标出,实际上其 sub_type
= 8(REDO_LOG_COMPELTE_SSTABLE
),也就是图中的 complete sstable 含义。
2.log_seq
=2 的 log 为第一条 Slog 记录的 NopLog,因此图中并没有显示该 log,而是只显示了 log_seq
=1 以及 log_seq
=3 的 redo log。
优化版本
由于一次事务的所有 redo log 数据量可能很小,如果每一次事务都直接增加 noplog 构造 ObStorageLogItem
刷盘,空间浪费且带宽浪费,因此可以聚合多次事务,flush 时将多次事务的 redo log 添加到队列,真正异步写盘时将多次事务的 logs 聚合成新的 ObStorageLogItem
,从而形成如下结构:
checkpoint
这里不介绍 checkpoint
的具体触发条件和执行流程,感兴趣的同学可以进一步阅读代码。
工作机制
为了加快恢复,Slog 提供 checkpoint
机制,生成 checkpoint
的过程也就是形成元数据宏块的过程,元数据宏块,也就是所谓的 Meta Block
,会写入 block_file
,Slog 同时会具有一个回放点(replay_start_point_
),标明了 Slog 内尚未生成 checkpoint
的剩余日志的起始偏移位置。Meta Block
的入口点以及 Slog 的回放点都保存在 Super Block
,想要了解更多相关内容可以阅读宏块或 OBServer 恢复的相关文章。
恢复
OBServer 对于 Slog 日志需要恢复的数据分为两部分,一部分是已经 checkpoint
形成 Meta Block
的基线元数据,另一部分是从 Slog 回放点开始的 Slog 日志,也就是增量元数据。
以上便是 OceanBase 数据库 Slog 的详细介绍,
更多知识和干货请点击链接查看原文及社区:
https://open.oceanbase.com/articles/8500124?sou0c001
最后的最后:
如果大家有任何疑问,可以通过以下方式与我们进行交流:
测试遇到问题?
企业用户想享受技术顾问的免费一对一咨询服务?
快加入 OB 创计划→
https://open.oceanbase.com/articles/8000125?sou0c001
钉钉群:33254054
今日之星,明日之星都不如你留下的星星(⭐️️)
我们想让 Github 上优质的开源项目被更多人看到。
文档都是我们精心整理。如果有帮助的话求个 star(◕ᴗ◕✿),鼓励鼓励我们哟!
也欢迎大家给我们提 issue,请点击 这里。运营小姐姐在此跪谢️️ ❥(^_-)
欢迎大家一起参与社区贡献,指南请参考看 这里
社区答疑:请点击 这里
版权声明: 本文为 InfoQ 作者【OceanBase 开源社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/aeba8fd6eef482ad5c91efbc3】。未经作者许可,禁止转载。
评论