在上一篇文章,咱们通过走读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的调度等代码,和你一起交换存储技术。
发表回复