一文讀懂 OceanBase 資料庫的SLog日誌

OceanBase開源社群發表於2021-10-08

作者簡介:鏡水,一個無限進步的資料庫學徒。
作者簡介:海芊,一個致力於當網紅的 OceanBase 文件工程師。

本文主要介紹 OceanBase 資料庫的 Slog 日誌,從程式碼層面剖析 Slog 日誌的模組結構和工作機制,幫助大家深入理解OceanBase 資料庫的 Slog日誌。

基本概念

我們都知道 OceanBase 資料庫的 Clog 日誌類似於傳統資料庫的 Redo 日誌,因此在分散式場景下需要多副本同步。而 Slog 不一樣,Slog 可以理解為伺服器的本地日誌,是一臺伺服器上一些全域性資訊變更操作(如新增租戶、分割槽建立和新增 SSTable 等)的 redo log。

一個伺服器只擁有一個 Slog 寫入流,也就是說同一臺伺服器上具有不同資源池的不同租戶並不具有單獨的 Slog 檔案,所有租戶的 Slog 寫入請求最後都會匯入伺服器所擁有的 Slog 檔案中。

一條 Slog 日誌記錄的格式為:


一文讀懂 OceanBase 資料庫的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:

struct ObLogEntry {
  ObRecordHeader header_;
  uint64_t seq_;  // 整個 log 的 log_seq
  int32_t cmd_;	  // 該 log 的型別,比如 OB_LOG_NOP 表示該 log 的型別為 NopLog
  // 具體見 enum LogCommand
}
struct ObRecordHeader {  
  int16_t magic_;            // magic number
  int16_t header_length_;    // header length
  int16_t version_;          // version
  int16_t header_checksum_;  // header checksum
  int64_t timestamp_;        //
  int32_t data_length_;      // length before compress
  int32_t data_zlength_;     // length after compress, if without compresssion
  							 // data_length_= data_zlength_
  int64_t data_checksum_;    // record checksum 
  ...
}

n *  log content 在記憶體中由  ObStorageLogActiveTrans 結構進行組織管理,以序列化的形式共同存放在  log_buffer_

struct ObStorageLogActiveTrans {
  enum common::LogCommand cmd_;			// 等同於 ObLogEntry 中的 cmd_
  int64_t log_count_;					// log_buffer_ 中包含的 log 數量
  common::ObLogCursor start_cursor_;	// log_buffer_ 中第一條 log 刷盤的位置
  										// 在 log_buffer_ 刷盤時被賦值
  ObStorageLogValidRecordEntry valid_record_;
  ObBaseStorageLogBuffer log_buffer_; 	// 所有 log content 的 buffer
  ...
}


每個  log content 由一個 log header( ObBaseStorageLogHeader 結構)和實際的 log data 組成。

struct ObBaseStorageLogHeader {
  ...
  int64_t trans_id_; // 事務 id,每次 Slog 的寫入對應有一個事務 id
  int64_t log_seq_;  // 每條 log content 在整個 logs 裡的 seq
  int64_t subcmd_;	 // 32bit(main_type)+32bit(sub_type)
  					 // main_type 表示日誌與什麼相關,sub_type 表示該日誌的具體操作型別
  					 // 如main_type = OB_REDO_LOG_PARTITION 表示日誌與分割槽相關,具體見 ObRedoLogMainType
  					 // 如sub_type = REDO_LOG_ADD_PARTITION 表示增加分割槽日誌,每種 main_type 都有單獨對應的 XXXRedoLogSubcmd
  int64_t log_len_;
  uint64_t tenant_id_;
  int64_t data_file_id_;
  ...
}

每次 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 構造完成後進行寫盤操作(涉及到非同步操作,這裡不做介紹)。

class ObStorageLogItem : public common::ObIBaseLogItem {
  ...
  bool is_inited_;
  bool is_local_;  // indicate whether buf_ is allocated locally or not
  int64_t buf_size_;
  char* buf_;
  int64_t len_;
  common::ObThreadCond flush_cond_;
  bool flush_finish_;
  int flush_ret_;
}

flush 流程的核心是通過寫流程中得到的 n *  log content 構造出一條完整的 Slog 記錄,通常可以直接用  ObStorageLogItem 結構指代。

示例


一文讀懂 OceanBase 資料庫的SLog日誌


上圖是 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,從而形成如下結構:


一文讀懂 OceanBase 資料庫的SLog日誌


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 的詳細介紹,

更多 知識和乾貨請點選連結 檢視原文及社群:

open.oceanbase.com/arti


最後的最後:

如果大家有任何疑問,可以通過以下方式與我們進行交流:

測試遇到問題?

企業使用者想享受技術顧問的免費一對一諮詢服務?

快加入 OB 創計劃→

釘釘群:33254054


一文讀懂 OceanBase 資料庫的SLog日誌



今日之星,明日之星都不如你留下的星星(⭐️️)

我們想讓  Github 上優質的開源專案被更多人看到。

文件都是我們精心整理。如果有幫助的話 求個star(◕ᴗ◕✿),鼓勵鼓勵我們喲!

也歡迎大家給我們   issue,請點選  這裡。運營小姐姐在此跪謝️️ ❥(^_-)

歡迎大家一起參與 社群貢獻,指南請參考看  這裡

社群答疑:請點選  這裡


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70005215/viewspace-2794952/,如需轉載,請註明出處,否則將追究法律責任。

相關文章