乐趣区

关于数据库:OceanBase-源码解读十二宏块的垃圾回收和坏块检查

此前,OceanBase 源码解读第十一篇《Location Cache 模块浅析》,为大家介绍了 observer 上的一个根底模块,为 SQL、事务、CLOG 等多个其余模块提供获取及缓存某个正本地位信息的能力的 Location Cache 模块。本期“源码解读”持续由 OceanBase 技术专家公祺为大家带来“存储层代码解读之「宏块的垃圾回收和坏块查看」”。

在解读之前,大家能够先回顾一下公祺的上篇对于《存储层代码解读之「宏块存储格局」》的内容。通过走读 OceanBase 宏块的代码,咱们对其存储格局有了初步的理解,晓得了微块作为读 I/O 最小单元,宏块是写 I/O 的最小单元。

明天咱们来考考大家,OceanBase 的垃圾回收(GC)和坏块查看是以微块还是宏块作为单位呢?答案很显著,就是宏块。因为既然是以宏块为单位写,所以必定是以宏块为单位进行清理和查看的。和本专题前几篇文章相似,本文次要通过走读相干代码,理解宏块垃圾回收和坏块查看的原理。

  • 注:本文所有的阐明及代码都是基于 v3.1.0_CE_BP1 版本的 OceanBase 开源代码。

任务调度

OceanBase 线程治理是封装好的,反对如下多种不同模式的线程治理:

// deps/oblib/src/lib/thread/thread_mgr.h
enum class TGType {
  INVALID,
  THREAD_POOL,    // 线程池
  OB_THREAD_POOL,  // OB 线程池
  TIMER,      // 定时工作
  TIMER_GROUP,    // 定时工作组
  QUEUE_THREAD,    // 队列线程调度
  DEDUP_QUEUE,    // 去重队列调度
  ASYNC_TASK_QUEUE,  // 异步工作队列调度
};

OceanBase 宏块的垃圾回收和坏块查看次要采纳 TIMER 定时任务调度,TIMER 的次要特点是启动一个后盾线程,周期性的对工作进行调度。对于其余的调度形式,后续会独自介绍。

既然采纳 TIMER 调度,那么其调度的机会也很显著,在启动 OBServer 过程时,就启动了一个后盾线程,周期性的进行垃圾回收和坏块查看,代码如下(省略了局部和垃圾回收无关的代码)。

// src/storage/blocksstable/ob_store_file.cpp
int ObStoreFile::open(const bool is_physical_flashback)
{
  int ret = OB_SUCCESS;
  bool is_replay_old = false;

  // 关上 StoreFile,并做相干初始化的动作
  ...
  // 执行首次 GC
  enable_mark_sweep();
  mark_and_sweep();
  // 启动 GC 定时工作线程
  // 调度 GC 的工作
  // 调度坏块查看的工作
  if (OB_FAIL(TG_START(lib::TGDefIDs::StoreFileGC))) {STORAGE_LOG(WARN, "The timer has not been inited,", K(ret));
  } else if (OB_FAIL(TG_SCHEDULE(lib::TGDefIDs::StoreFileGC, gc_task_, RECYCLE_DELAY_US, true))) {STORAGE_LOG(WARN, "Fail to schedule gc task,", K(ret));
  } else if (OB_FAIL(TG_SCHEDULE(lib::TGDefIDs::StoreFileGC, inspect_bad_block_task_, INSPECT_DELAY_US, true))) {STORAGE_LOG(WARN, "Fail to schedule bad_block_inspect task,", K(ret));
  } else {// ...

垃圾回收和坏块查看线程的生命周期和 OBServer 过程是统一的,它们都会在 ObStoreFile::destroy 中进行。

宏块的垃圾回收

宏块垃圾回收的工作定义如下:

// src/storage/blocksstable/ob_store_file.h
// ObTimerTask 为定时工作的基类
class ObStoreFileGCTask : public common::ObTimerTask {
public:
  ObStoreFileGCTask();
  virtual ~ObStoreFileGCTask();
  // 继承至 ObTimerTask,GC 的次要执行函数在:ObStoreFile::mark_and_sweep()
  virtual void runTimerTask() 
{OB_STORE_FILE.mark_and_sweep();
  }
};

ObStoreFile::mark_and_sweep 次要逻辑是对所有在用的宏块进行打标,并查看未打标的宏块,将其中 ref_cnt_ 为 0 的进行垃圾回收。

// src/storage/blocksstable/ob_store_file.cpp

void ObStoreFile::mark_and_sweep()
{
  int ret = OB_SUCCESS;
  MacroBlockId macro_id;
  bool is_freed = false;

  // 查看 GC 工作的开关
  if (!is_mark_sweep_enabled()) {STORAGE_LOG(INFO, "mark and sweep is disabled, do not mark and sweep this round");
  } else {
    // 标识开始进行 gc
    set_mark_sweep_doing();

    // 1. 采纳 bitmap 对所有在应用的宏块进行打标
    //   a. data 块的打标是通过一个宏块迭代器实现的
    //   b. meta 块的打标比较简单,因为可用的 meta 块是记录在一个数组中的
    // 通过打标之后咱们就失去所有无效的宏块的 bitmap
    if (OB_FAIL(mark_macro_blocks())) {STORAGE_LOG(WARN, "Fail to mark macro blocks,", K(ret));
    } 
    
    // 2. 遍历所有的宏块,进行垃圾清理
    begin_time = end_time;
    if (OB_SUCC(ret)) {for (int64_t i = 0; i < store_file_system_->get_total_macro_block_count() && is_mark_sweep_enabled(); ++i) {if (bitmap_test(i)) {
          // 无效的宏块,做个二次查看
          if (macro_block_info_[i].is_free_) {
            // BUG, should not happen
            STORAGE_LOG(ERROR, "the macro block is freed,", K(i));
          }
        } else {
          // 无用的宏块,进行 GC
          if (0 == ATOMIC_LOAD(&(macro_block_info_[i].ref_cnt_))) {
            // ref 为 0,即可进行清理
            if (!macro_block_info_[i].is_free_) {
              // 开释宏块
              //   a. 将该宏块信息进行标注:is_free_=true
              //   b. 将该宏块 id 放入到 free_block_array_中,后续进行二次重用
              free_block((uint32_t)i, is_freed);
              // ...
            }
          }
        }
      }
    }
    set_mark_sweep_done();}
}

坏块查看

坏块查看工作也会在 StoreFileGC 线程做,该工作也是一个定时工作,它的定义如下:

class ObFileSystemInspectBadBlockTask : public common::ObTimerTask {
public:
  // 继承至 ObTimerTask,次要执行函数在 inspect_bad_block
  virtual void runTimerTask()
{inspect_bad_block();
  }
private:
  // 遍历所有的宏块,通过 check_macro_block 进行宏块查看
  // 因为坏块查看比拟消耗资源,inspect_bad_block 中也做一些流控逻辑
  void inspect_bad_block();
  // 对某 1 个宏块进行查看,查看的次要内容有://   a. 宏块 id、宏块元数据
  //   b. 通过 check_data_block 对宏块数据进行深度查看
  int check_macro_block(const ObMacroBlockInfoPair& pair, const storage::ObTenantFileKey& file_key);
  int check_data_block(const MacroBlockId& macro_id, const blocksstable::ObFullMacroBlockMeta& full_meta,
      const storage::ObTenantFileKey& file_key);
private:
  // 工具类,宏块查看次要函数,前面会详细分析
  ObSSTableMacroBlockChecker macro_checker_;
};

坏块查看的代价广泛较高,因为可能须要从磁盘读取所有宏块的内容,并进行 checksum 校验,会占用大量的 CPU、I/O 能力,进而影响业务流量。所以,OceanBase 的坏块查看反对不同级别的查看,DBA 能够依据零碎本身的特点,比方磁盘、CPU 的具体配置等,选取适合的级别进行坏块查看,反对级别具体如下。

// src/storage/blocksstable/ob_macro_block_checker.h
enum ObMacroBlockCheckLevel {
  CHECK_LEVEL_NOTHING = 0,// 不做坏块查看
  CHECK_LEVEL_MACRO,      // 查看宏块的 checksum,如果宏块的 checksum 不存在间接返回胜利
  CHECK_LEVEL_MICRO,      // 查看微块的 checksum
  CHECK_LEVEL_ROW,        // 查看每个 column 的 checksum,会遍历每一行的每一列
  CHECK_LEVEL_AUTO,       // 默认的查看级别,如果宏块的 checksum 存在,就查看宏块,如果宏块的 checksum 不存在,就查看微块
  CHECK_LEVEL_MAX
};

接下来咱们看下 ObSSTableMacroBlockChecker 的次要内容:

// src/storage/blocksstable/ob_macro_block_checker.h
// note: 该 class 中的函数是线程不平安的
class ObSSTableMacroBlockChecker {
public:
  // check 是宏块查看次要函数,会依据 check_level 进行不同级别的查看
  // 具体内容的查看见 private 中的函数
  int check(const char* macro_block_buf, const int64_t macro_block_buf_size, const ObFullMacroBlockMeta& meta,
      ObMacroBlockCheckLevel check_level = CHECK_LEVEL_AUTO);
private:
  // 查看宏块数据 buffer 的 checksum,并和 header 中 checksum 中做比拟
  int check_macro_buf(const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const int64_t macro_block_buf_size);
  // 查看宏块数据的 header,具体的查看内容包含://   a. check_sstable_data_header:查看 sstable 数据的 header
  //   b. check_lob_data_header:查看 log 数据的 header
  //   c. check_bloomfilter_data_header:查看 header 中的 bloomfilter
  int check_data_header(const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf,
      const int64_t macro_block_buf_size, const ObFullMacroBlockMeta& meta);
  // 查看一般宏块的具体数据,遍历宏块中每一个微块,微块的查看见上面的 check_micro_data
  int check_data_block(const char* macro_block_buf, const int64_t macro_block_buf_size,
      const ObFullMacroBlockMeta& meta, const bool need_check_row);
  // 查看 LOB 宏块的具体数据,包含其中所有的微块
  int check_lob_block(const char* macro_block_buf, const int64_t macro_block_buf_size, const ObFullMacroBlockMeta& meta);
  // 查看微块的具体数据,包含其中每一行的每一列的 checksum
  int check_micro_data(const char* micro_buf, const int64_t micro_buf_size, const ObFullMacroBlockMeta& meta, int64_t* checksum);
  // 查看 sstable 宏块 header
  int check_sstable_data_header(const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const ObFullMacroBlockMeta& meta);
  // 查看 LOB 宏块 header
  int check_lob_data_header(const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const ObFullMacroBlockMeta& meta);
  // 查看 bloomfilter 中的 header
  int check_bloomfilter_data_header(const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const ObFullMacroBlockMeta& meta);
};

写在最初

相较于宏块、微块的编码来说,宏块的垃圾回收和坏块查看的代码大抵逻辑简略易懂,但其中也不乏精美的细节,比方,垃圾回收中的 bitmap 的应用和坏块查看中的流控,如果你有趣味的话能够具体浏览上述代码。

咱们将在下一篇文章会持续浏览 sstable 的合并、dag 的调度等代码,和你一起交换存储技术,敬请关注!

退出移动版