RDS AliSQL 面向 Binlog 的效能優化大揭密(上)—— 極致 IO 優化

語言: CN / TW / HK

RDS AliSQL 簡介

AliSQL是阿里雲RDS團隊深度優化的獨立 MySQL 分支,除了社群版的所有功能外,AliSQL提供了類似於MySQL企業版的諸多功能,如企業級備份恢復、審計日誌、執行緒池、Binlog in Redo 等。RDS MySQL使用AliSQL核心,為使用者提供了MySQL所有的功能,同時提供了企業級的安全、備份、恢復、監控、效能優化、只讀例項、Serverless等高階特性。

Binlog 是謂何物 

Binlog (Binary log)是 MySQL Server 層維護的一種二進位制日誌,以事務級別記錄了對資料庫的所有修改操作。

事務級別是指:一個事務的日誌,是在事務提交時被寫入 binlog 檔案中的。具體來說,事務在執行過程中,會不斷的生成 binlog events,暫存在 session 級別的 binlog cache 中;事務提交時,會一次將 binlog cache 中所有內容寫到 binlog 檔案中。

Binlog 的一致性保證

在 MySQL 中,binlog 是為數不多可以做到 “準” 的日誌(另一個 “準” 的是 redo log),即保證日誌中存在的修改,資料中一定存在,反之亦然。也正因為它可以做到 “準”,MySQL 才可以基於 binlog 做複製和備份。

Sync_binlog

Sync_binlog 是 binlog 相關的一個重要引數,它控制了 binlog 的刷盤方式。在瞭解這個引數的具體含義前,先要了解一下 Linux 系統的 page cache 機制。

Page Cache

我們知道計算機的儲存介質籠統上說分三層,其讀寫速度從高到低分別是 cpu cache,memory 和磁碟。Linux 核心為了優化檔案的讀寫速度,在磁碟之前加入了一層快取,叫做 page cache。

Page cache 本質上是由作業系統直接管理的一塊記憶體空間,應用程式寫檔案時,會先寫到 page cache 上,然後作業系統擇機進行刷盤。當然,這樣的機制會帶來的一個隱患,即當機器 crash 時,沒有從 page cache 刷到磁碟的資料會丟失。很多應用程式不能容忍這樣的資料丟失,因此作業系統也為應用程式提供了主動刷盤的介面。

Flush 和 Sync 的含義

在 binlog 中,寫 binlog events 到 binlog 檔案的 page cache 的行為被稱為 flush,binlog 檔案刷盤的行為被稱為 sync。需注意,這個叫法是 binlog 語境中的習慣,redo log 中的叫法又有不同。

引數含義

Sync_binlog 引數控制了 binlog 刷盤的頻率,當配置為 0 時,binlog 不主動進行刷盤;當配置為 n (n > 0) 時,binlog 每 n 個事務一起刷盤。

當 sync_binlog 設定為 1 時,每個事務提交時,都會主動 binlog 刷盤,這種配置下,不會出現已提交的事務的 binlog 丟失的現象,基於 binlog 的複製和備份都能保證可靠性。配合 innodb 中的引數innodb_flush_log_at_trx_commit = 1(每個事務 redo log 都主動刷盤),可以達到資料和日誌完全一致的高可靠性,這種配置俗稱雙一,下文討論過程中,我們都預設例項處於雙一配置。

兩階段提交

在雙一的配置下,MySQL 使用兩階段機制,保證 Binlog 與 innodb 的崩潰一致性(crash safe)。也就是說,無論例項在什麼時間 crash,重啟後通過 crash recovery,Binlog 與 innodb 都可以達到一致性狀態。

上圖是一個兩階段提交的示意圖,所謂兩階段,就是將事務提交分為 prepare 和 commit 兩個階段,將事務分為了 active 和 prepared 兩種狀態。

Prepare 階段

Prepare 階段最主要的動作是將事務從 active 狀態置為 prepared 狀態,並將 undo 回滾段也置為 prepared。這個動作的意義是,標記事務所有操作都已經結束,在 crash recovery 過程中,處在 prepared 狀態的事務是可以回滾也可以提交的(active 狀態事務只能回滾不能提交)。有了這個狀態,redo log 和 binlog 才能在 commit 階段和 crash recovery 過程中相互協調。

需注意,這個階段修改事務狀態的動作是記憶體態的,只有記錄 undo 回滾段的 redo log 刷盤,事務狀態才算被持久化到磁碟。這是由於 innodb 的 WAL 機制導致的,有興趣的同學可以去查閱相關資料學習。

在早期的 MySQL 上,prepare 階段還會進行 redo log 的刷盤操作。但隨著 binlog group commit 優化的誕生,刷盤操作的效能有了巨大提升,因此 redo log 的刷盤操作被移到了 commit 階段的 binlog group commit 流程中以優化 IO 效能。這部分細節將在 binlog group commit 優化的章節中詳細講解。值得一提的是,這個改動和 AliSQL 的早期成員印風有很大關聯,他在效能測試中首先發現了這個問題,並將 idea 和 code patch 一併貢獻給了 MySQL 官方。對這段歷史感興趣的可以參照 http://bugs.mysql.com/bug.php?id=73202。

Commit 階段

Commit 階段除了事務提交的操作外,還包括 redo log 的刷盤,binlog 的寫入和刷盤。這裡對這兩個日誌檔案的操作順序很重要。

Binlog 刷盤,就意味著事務已經可以被傳給從庫,此時就算例項沒有完成 commit 就 crash 了,crash recovery 過程中也需要能夠提交此事務,這樣才不會造成主從不一致。而事務在 crash recovery 過程中事務可提交,就需要事務的 prepared 狀態已經被持久化到磁碟,也就是 redo log 已經刷盤。因此必須先將 redo log 刷盤,才可以寫 binlog 檔案並刷盤。因此,在提交階段,以 binlog 刷盤完成為標誌,刷盤完成前發生 crash,事務回滾,刷盤完成後發生 crash,事務提交。

在完成 binlog 刷盤後,事務就可以提交。提交時會把事務從 prepared 狀態改為 commit 狀態,這個操作是需要寫 redo log 的,但是事務完成提交併不需要等待這個 redo log 刷盤。剛才說過,只要 binlog 刷盤完成,就可以認為這個事務已經持久化的提交了,因此不必再等待一次 redo log 刷盤,直接返回事務提交成功即可。

各時間點 Crash 分析

當例項發生 crash 時(我們這裡假設一種最嚴重的 crash——機器故障導致重啟,所有page cache全丟)

  • 如果 redo log 刷盤沒有完成,事務的 prepared 狀態還沒有刷盤,還是 active 的狀態,直接回滾即可。

  • 如果 redo log 刷盤完成,binlog 刷盤沒有完成,重啟時事務是 prepared 狀態,在啟動階段這樣的事務又叫未決事務,可以選擇提交或回滾。通過掃描 binlog,發現事務沒有完整的存在於 binlog 中,因此選擇回滾;

  • 如果 binlog 完成刷盤,重啟時事務是 prepared 狀態,binlog 中也完整的記錄了這個事務,必須選擇提交。

IO 效能問題

上述機制中,為了保證 binlog 的完整和 crash safe,每個事務在提交時,都需要等待兩次 IO 操作,一次 redo log,一次 binlog。這導致了極大的效能問題。

為了優化效能,MySQL 官方引入了 Binlog Group Commit 優化來合併 IO 操作。

Binlog Group Commit 優化

BGC (Binlog Group Commit)優化是 MySQL 官方做的,針對 binlog 提交階段的一個性能優化,目的是合併 IO 操作。

合併 IO 操作

在檔案系統中,IO 操作是成批進行的,在一般情況下,10 次 1KB 的 IO 要比 1 次 10KB 的 IO 慢得多,這就是合併 IO 操作的意義。這個思想在 MySQL server 和 innodb 的眾多設計中都有體現。

Binlog Group Commit 流程

Group Commit 顧名思義,就是把若干事務組成一個組,一起提交。MySQL 將 Binlog 提交階段分成 flush stage,sync stage 和 commit stage。

Flush stage

Flush stage 中包含 sync redo 和 flush binlog。進入提交階段的事務,首先會等待進入 flush stage。為什麼會等待呢?具體來說,正在 flush stage 中進行 sync redo 和 flush binlog 的 group,會持有一把 lock_log 鎖,這樣就阻塞住了新來的事務,它們需要等待當前 group 結束釋放 lock_log 鎖,才能進入 flush stage。

在等待的過程中,系統可以積攢很多等待進入 flush stage 的事務,把他們全部組成一個 group。Group 中的第一個事務作為 leader,flush stage 中所有的操作都由 leader 的執行緒完成。這樣一來,原先多個事務進行多次 sync redo 的操作,就變成了一次 sync redo,最大限度合併了 IO 操作。

MySQL-80 上 redo log 引入了無鎖化設計後,sync redo 的動作變為後臺執行緒完成 。Flush stage 中主動進行 sync redo 的操作就變為了等待後臺執行緒完成 sync redo。這種設計下,group commit 也非常有意義,因為等待後臺執行緒是需要拿鎖的,很多事務獨立等待時,會造成嚴重的鎖衝突。在這種情況下 group commit 實質上是合併了鎖等待,避免了鎖衝突。

Sync stage

Sync stage 中包含 sync binlog 的操作。完成 flush stage 的 flush group,在進入 sync stage 前還需要再次組隊,由幾個 flush group 組成一個 sync group,進一步合併 IO。

Sync stage 組隊的機制和 flush stage 類似,由正在 sync binlog 的 sync group 持有一把 lock_sync 鎖,準備進入 sync stage 的 flush group 在等待這把鎖的過程中積攢,組成一個 sync group。當上一組 sync binlog 完成 lock_sync 被釋放,由本組第一個 flush group 的 leader 作為新組的 leader,完成本組的 sync stage。

Commit stage

Commit stage 中包含事務的提交操作。Commit stage的目的並非合併 IO,而是為了讓 binlog 中事務的順序和事務真實提交的順序一致。

在開啟了 binlog_order_commits 或者 clone 過程中需要依賴提交順序時,完成 sync stage 的事務會再次進入 commit stage;沒有開始順序提交的例項,commit stage會被跳過,每個事務自己進行事務提交。

Binlog in Redo 

極致 IO 優化

B GC 優化通過分組的方式,可以將很多小的 IO 操作合併,但是事務提交需要等待的 IO 次數(sync redo 和 sync binlog 兩次 IO)並沒有改變。 為了進一步優化 IO,AliSQL 引入了 Binlog in redo 優化,旨在將事務提交階段的兩次 IO 操作合併為一次 IO 操作。

而具體如何實施優化,以及如何驗證優化效果,會在後續文章中進行講述。

Binlog in redo 架構

前章曾提到過 Innodb 的 WAL 機制,innodb 寫任何資料前,都需要先寫下資料的 redo log,隨後提交時,只需要同步等待 redo log 刷盤,資料後續非同步的刷盤即可。即便出現了 crash,只要 redo log 完整,資料也可被準確無誤的恢復。

Binlog in redo 顧名思義,將 binlog 寫到 redo log 中。 在這個架構下,binlog 對於 redo log 來說也是一份“資料”,受到 redo log 的保護; binlog 的刷盤由非同步執行緒完成,事務提交過程中,無需等待 binlog 刷盤。 如果發生 crash,根據 redo log 恢復binlog 檔案即可。 這樣一來,提交階段需要等待的 IO 次數就變為了 1 次,能夠大幅提升效能。

Crash Recovery 機制

Binlog in redo 架構中,當例項 crash 後,binlog 中的內容有可能丟失,因此需要在重啟後的 recovery 過程中補齊。Server 層中實現了一個 Binlog applier 去補齊 binlog。

在 crash recovery 期間 apply redo 時,每次在 redo log 中讀到 binlog event,就解析他的 binlog position,如果這個位置已經存在於 binlog檔案中,就不需要回填,如果這個位置不在了,就需要回填一下。

效能優化效果

測試環境:32Core, 64G Ram, ESSD儲存。

測試工具:sysbench

oltp_update_non_index

oltp_insert

oltp_write_only

結論

Binlog In Redo 功能在不損失可靠性的前提下,減少了 1 次IO。在不超過 256 併發的情況 下,Binlog In Redo 功能對效能的提升和延遲的降低都非常顯著。但是對於大併發的場景,效能提升效果有限。這是由於大併發下,BGC 合併 IO 的作用更加明顯,Binlog 提交期間的瓶頸來到了序列化的 Flush binlog。為了優化這一瓶頸,AliSQL 引入了 Binlog Parallel Flush 優化,最終使例項達到全業務場景的大幅效能提升。

欲知後事如何,請聽下回分解。

作者簡介

武根澤(子堪) RDS MySQL核心團隊核心研發,擅長 MySQL 核心程式碼研發,效能調優和技術問題探索,在 MySQL Binlog,Innodb 等領域多有涉獵。 關於RDS MySQL核心方面的問題,歡迎致電: [email protected] 諮詢。

推薦閱讀

點選 “閱讀原文” 檢視雲資料庫 RDS SQL Server 版更多資訊