• 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的调度等代码,和你一起交流存储技术。

  • 相关阅读:
    一种改进的鲸鱼优化算法-附代码
    第十八章 Servlet
    一文聊透 Netty 核心引擎 Reactor 的运转架构
    【软件与系统安全】栈溢出利用的分析
    导致系统性能失败的10个原因
    Go语言学习基础(二)编写注意,数据类型,关键字,标识符等
    学习MySQL的第一天:MySQL概述(基础篇)
    代买随想录二刷day57
    linux环境下如果掌控了系统root账户就能对上面安装的MySQL数据为所欲为了吗
    Python入门|零基础教程
  • 原文地址:https://blog.csdn.net/OceanBaseGFBK/article/details/127041554