OceanBase源码解读:Palf日志引擎 log_engine.cpp

OceanBase 的 Paxos 一致性协议由两层构成:上层是 PalfHandleImpl 负责副本间协议协调(选举、日志序号分配、确认),下层则是 LogEngine 负责把已经达成共识的日志条目落盘、读取、推送给 follower。LogEngine 是 PalfHandleImpl 与物理块存储之间的”单机适配层”,本文聚焦其核心实现 log_engine.cpp(1915 行),梳理它的架构位置、关键数据结构、初始化与重启路径、以及面向协议层的高频入口。

架构位置

LogEngine 位于 src/logservice/palf/ 目录,每条 Paxos 副本持有一个实例。它的上下游分工清晰:

  • 上层:PalfHandleImpl / PalfReplicator 提交日志、变更成员、发起 truncate/flashback;
  • 下层:LogIOWorker(异步 IO 派发)、LogStorage(redo 块管理)、LogMetaStorage(元数据块管理)、LogNetService(日志推送 RPC)。

这样的分层让”协议层”只关心 LSN 连续性,”IO 层”只关心 block 持久化,二者通过异步任务对象解耦。

关键数据结构

LogEngine 内部聚合了五个核心对象,每一个都对应一类明确的职责:

  • log_meta_:LogMeta,封装五类元数据 —— prepare_meta(最新提案号)、config_meta(副本列表)、mode_meta(成员变更阶段)、snapshot_meta(基线快照)、replica_property_meta(副本属性)。五类元数据被打包成单一 LogMeta,由 log_meta_lock_ 串行化保护,避免分散加锁导致死锁与顺序混乱。
  • log_storage_:redo 日志的物理块管理,按 log_storage_block_size 切块循环利用。
  • log_meta_storage_:元数据块的物理管理,体量小、刷盘频率低,与 redo 物理隔离便于独立调优。
  • log_io_worker_:所有写盘 IO 的统一派发器,主链路(提交流水)把任务投递出去后立即返回,不阻塞等磁盘。
  • log_net_service_:负责把日志条目以 RPC 推送给 follower。

初始化与重启路径

LogEngine 提供 init/load 两条入口:

init 路径(首次创建)顺序固定为参数校验 → log_meta_storage_.init → log_storage_.init → log_net_service_.init → append_log_meta_(log_meta)。最后一步把首条 LogMeta 写入 meta_storage,确立副本的初始成员与状态。失败时除 OB_INIT_TWICE 外会自动调 destroy() 清理半初始化资源。

load 路径(重启)要复杂得多。它先 load meta_storage,再用 construct_log_meta_ 读取最后一条 LogMetaEntry 还原内存 LogMeta;接着 load redo log_storage;之后调 try_clear_up_holes_and_check_storage_integrity_ 补齐空洞;最后 integrity_verify_ 校验四种 meta/redo 组合的合法性。该函数对”meta 空 + redo 空”的场景返回 is_integrity=false,让上层 PalfEnvImpl 把目录挪走并从副本列表移除 —— 这种设计让运维误删场景下 LogEngine 不至于启动失败,而是被上层优雅回收。

int LogEngine::integrity_verify_(const LSN &last_meta_entry_start_lsn,
                                 const LSN &last_group_entry_header_lsn,
                                 bool &is_integrity)
{
  int ret = OB_SUCCESS;
  is_integrity = true;
  bool meta_is_valid = last_meta_entry_start_lsn.is_valid();
  bool redo_is_valid = last_group_entry_header_lsn.is_valid();
  // 1. meta 空 redo 非空 → 致命错误
  // 2. 双空 → 非完整性,挪走目录
  // 3. meta 非空 redo 空 → 正常
  // 4. 双非空 → 正常
  if (false == meta_is_valid && true == redo_is_valid) {
    ret = OB_ERR_UNEXPECTED;
  } else if (false == meta_is_valid && false == redo_is_valid) {
    is_integrity = false;
  }
  return ret;
}

高频入口:提交 redo 日志

协议层最常用的接口是 submit_flush_log_task,它把 FlushLogCbCtx 和 LogWriteBuf 打包成 LogIOFlushLogTask,由 log_io_worker_ 异步派发,主线程不阻塞等磁盘:

int LogEngine::submit_flush_log_task(const FlushLogCbCtx &flush_log_cb_ctx,
                                     const LogWriteBuf &write_buf)
{
  int ret = OB_SUCCESS;
  LogIOFlushLogTask *flush_log_task = NULL;
  if (IS_NOT_INIT) {
    ret = OB_NOT_INIT;
  } else if (false == flush_log_cb_ctx.is_valid() || false == write_buf.is_valid()) {
    ret = OB_INVALID_ARGUMENT;
  } else if (OB_FAIL(generate_flush_log_task_(flush_log_cb_ctx, write_buf, flush_log_task))) {
  } else if (OB_FAIL(log_io_worker_->submit_io_task(flush_log_task))) {
  }
  if (OB_FAIL(ret) && OB_NOT_NULL(flush_log_task)) {
    alloc_mgr_->free_log_io_flush_log_task(flush_log_task);
  }
  return ret;
}

对应的 append_log 是更底层的同步入口(writev 支持 vector write),二者配合实现了”高频派发 + 批量落盘”的写盘模式。任务执行到 block 切换时会回调 update_manifest,把新的 block_id 推给 meta_storage,从而维持 redo 与 meta 的 block 进度同步。

五类 meta 的统一提交模式

成员变更、配置变更、成员变更阶段、snapshot、副本属性这五类元数据落盘都走同一个模式:

  1. 拿 ObSpinLockGuard(log_meta_lock_),串行化所有 meta 操作;
  2. log_meta_.update_log_xxx_meta 修改内存对象;
  3. submit_flush_meta_task_ 内部走 log_io_worker_ 异步刷盘;
  4. 失败时(如 snapshot_meta)显式回滚到 prev_log_snapshot_meta,避免下次请求被错误状态干扰。

submit_purge_throttling_task 还在这之上加了节流:距上次成功 purge 小于 PURGE_THROTTLING_INTERVAL 且非强制类型时直接跳过,避免频繁 purge 抖动 IO;need_force_purge 让紧急恢复路径可以绕过节流。

任务工厂的复用模式

generate_flush_log_task_ 是所有”生成 IO 任务”函数的统一模板:alloc_mgr_ 多租户分配 → task 携带 palf_id_/palf_epoch_ → 失败时显式回收。这一模式被 generate_truncate_log_task_ / generate_truncate_prefix_blocks_task_ / generate_flashback_task_ / generate_purge_throttling_task_ / generate_fill_cache_task_ 完整复用,是 palf 模块的”任务工厂”通用模板,palf_id_/palf_epoch_ 在 epoch 切换时区分复用与重建。

小结

LogEngine 的设计核心是解耦:协议层与物理层之间通过异步 IO 任务对象隔离,五类元数据通过单锁聚合统一管理,redo 与 meta 分块管理便于独立调优。完整走通 init / load / submit_flush_log_task / update_manifest / 五类 meta 提交 / 任务工厂这六条主线,就能掌握 OceanBase 单机日志引擎的完整骨架,也是阅读更上层的 PalfHandleImpl 副本协议实现的基础。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注