MySQL主從複製延遲解決方案

7small7發表於2023-03-16

前面一篇,我們學習到了MySQL多版本併發控制(MVCC)實現原理,這一篇我們接著學習MySQL主從複製模式下的延遲解決方案。

MySQL主從延遲是指從庫的資料同步比主庫略有延遲,造成資料差異。MySQL主從複製模式一般採用以下方法降低延遲:

1、最佳化網路環境:主從複製時,減小主從伺服器之間網路延遲對資料庫同步的影響。可以考慮最佳化網路之間連線的頻寬、增加從庫的硬體效能等。

2、增加從庫數量:增加從庫數量可以增加資料同步的速度和可靠性,同時也能減少每個從庫的負擔,提高從庫響應速度。

3、調整資料庫相關引數:可以調整一些MySQL資料庫中的相關引數,比如調整binlog格式、binlog緩衝區大小、innodb_flush_log_at_trx_commit等引數,採用半同步模式,以加快資料的同步速度。

4、分割槽資料庫:將資料庫分成多個區,每個從庫只複製自己所需要的資料區,可以有效的減少排隊堵塞、網路傳輸等方面的延遲問題。

綜上所述,最佳化網路環境、增加從庫數量、調整資料庫相關引數、分割槽資料庫等方法可以有效的降低MySQL主從複製模式的延遲。

什麼是主從延遲

在討論如何解決主從延遲之前,我們先了解下什麼是主從延遲。

為了完成主從複製,從庫需要透過 I/O 執行緒獲取主庫中 dump 執行緒讀取的 binlog 內容並寫入到自己的中繼日誌 relay log 中,從庫的 SQL 執行緒再讀取中繼日誌,重做中繼日誌中的日誌,相當於再執行一遍 SQL,更新自己的資料庫,以達到資料的一致性。

與資料同步有關的時間點主要包括以下三個:

1、主庫執行完一個事務,寫入 binlog,將這個時刻記為 T1;

2、之後傳給從庫,將從庫接收完這個 binlog 的時刻記為 T2;

3、從庫執行完成這個事務,將這個時刻記為 T3。

所謂主從延遲,就是同一個事務,從庫執行完成的時間與主庫執行完成的時間之差,也就是 T3 - T1。

可以在備庫上執行 show slave status 命令,它的返回結果裡面會顯示 seconds_behind_master,用於表示當前備庫延遲了多少秒。
seconds_behind_master 的計算方法是這樣的:

1、每個事務的 binlog 裡面都有一個時間欄位,用於記錄主庫上寫入的時間;

2、備庫取出當前正在執行的事務的時間欄位的值,計算它與當前系統時間的差值,得到 seconds_behind_master。

在網路正常的時候,日誌從主庫傳給從庫所需的時間是很短的,即 T2 - T1 的值是非常小的。也就是說,網路正常情況下,主從延遲的主要來源是從庫接收完 binlog 和執行完這個事務之間的時間差。

由於主從延遲的存在,我們可能會發現,資料剛寫入主庫,結果卻查不到,因為可能還未同步到從庫。主從延遲越嚴重,該問題也愈加明顯。

主從延遲的來源

主庫和從庫在執行同一個事務的時候出現時間差的問題,主要原因包括但不限於以下幾種情況:

1、有些部署條件下,從庫所在機器的效能要比主庫效能差。

2、從庫的壓力較大,即從庫承受了大量的請求。

3、執行大事務。因為主庫上必須等事務執行完成才會寫入 binlog,再傳給備庫。如果一個主庫上語句執行 10 分鐘,那麼這個事務可能會導致從庫延遲 10 分鐘。

4、從庫的並行複製能力。

主從延遲的解決方案

解決主從延遲主要有以下方案:

1、配合 semi-sync 半同步複製;

2、一主多從,分攤從庫壓力;

3、強制走主庫方案(強一致性);

4、sleep 方案:主庫更新後,讀從庫之前先 sleep 一下;

5、判斷主備無延遲方案(例如判斷 seconds_behind_master 引數是否已經等於 0、對比位點);

6、並行複製 — 解決從庫複製延遲的問題;

這裡主要介紹我在專案中使用的幾種方案,分別是半同步複製、實時性操作強制走主庫、並行複製。

半同步複製

MySQL 有三種同步模式,分別是:

「非同步複製」:MySQL 預設的複製即是非同步的,主庫在執行完客戶端提交的事務後會立即將結果返給客戶端,並不關心從庫是否已經接收並處理。這樣就會有一個問題,一旦主庫當機,此時主庫上已經提交的事務可能因為網路原因並沒有傳到從庫上,如果此時執行故障轉移,強行將從提升為主,可能導致新主上的資料不完整。

「全同步複製」:指當主庫執行完一個事務,並且所有的從庫都執行了該事務,主庫才提交事務並返回結果給客戶端。因為需要等待所有從庫執行完該事務才能返回,所以全同步複製的效能必然會收到嚴重的影響。

「半同步複製」:是介於全同步複製與全非同步複製之間的一種,主庫只需要等待至少一個從庫接收到並寫到 Relay Log 檔案即可,主庫不需要等待所有從庫給主庫返回 ACK。主庫收到這個 ACK 以後,才能給客戶端返回 “事務完成” 的確認。

MySQL 預設的複製是非同步的,所以主庫和從庫的資料會有一定的延遲,更重要的是非同步複製可能會引起資料的丟失。但是全同步複製又會使得完成一個事務的時間被拉長,帶來效能的降低。因此我把目光轉向半同步複製。從 MySQL 5.5 開始,MySQL 以外掛的形式支援 semi-sync 半同步複製。

相對於非同步複製,半同步複製提高了資料的安全性,減少了主從延遲,當然它也還是有一定程度的延遲,這個延遲最少是一個 TCP/IP 往返的時間。所以,半同步複製最好在低延時的網路中使用。

需要注意的是:

1、主庫和從庫都要啟用半同步複製才會進行半同步複製功能,否則主庫會還原為預設的非同步複製。

2、如果在等待過程中,等待時間已經超過了配置的超時時間,沒有收到任何一個從庫的 ACK,那麼此時主庫會自動轉換為非同步複製。當至少一個半同步從節點趕上來時,主庫便會自動轉換為半同步複製。

半同步複製的潛在問題

在傳統的半同步複製中(MySQL 5.5 引入),主庫寫資料到 binlog,並且執行 commit 提交事務後,會一直等待一個從庫的 ACK,即從庫寫入 Relay Log 後,並將資料落盤,再返回給主庫 ACK,主庫收到這個 ACK 以後,才能給客戶端返回 “事務完成” 的確認。

這樣會出現一個問題,就是實際上主庫已經將該事務 commit 到了儲存引擎層,應用已經可以看到資料發生了變化,只是在等待返回而已。如果此時主庫當機,可能從庫還沒寫入 Relay Log,就會發生主從庫資料不一致。

為瞭解決上述問題,MySQL 5.7 引入了增強半同步複製。針對上面這個圖,“Waiting Slave dump” 被調整到了 “Storage Commit” 之前,即主庫寫資料到 binlog 後,就開始等待從庫的應答 ACK,直到至少一個從庫寫入 Relay Log 後,並將資料落盤,然後返回給主庫 ACK,通知主庫可以執行 commit 操作,然後主庫再將事務提交到事務引擎層,應用此時才可以看到資料發生了變化。

當然之前的半同步方案同樣支援,MySQL 5.7.2 引入了一個新的引數 rpl_semi_sync_master_wait_point 進行控制。這個引數有兩種取值:

1、AFTER_SYNC:這個是新的半同步方案,Waiting Slave dump 在 Storage Commit 之前。

2、AFTER_COMMIT:這個是老的半同步方案。

在 MySQL 5.5 – 5.6 使用 after_commit 的模式下,客戶端事務在儲存引擎層提交後,在主庫等待從庫確認的過程中,主庫當機了。此時,結果雖然沒有返回給當前客戶端,但事務已經提交了,其他客戶端會讀取到該已提交的事務。如果從庫沒有接收到該事務或者未寫入 relay log,同時主庫當機了,之後切換到備庫,那麼之前讀到的事務就不見了,出現了幻讀,也就是資料丟失了。

MySQL 5.7 預設值則是 after_sync,主庫將每個事務寫入 binlog,傳給從庫並重新整理到磁碟 (relay log)。主庫等到從庫返回 ack 之後,再提交事務並且返回 commit OK 結果給客戶端。 即使主庫 crash,所有在主庫上已經提交的事務都能保證已經同步到從庫的 relay log 中,解決了 after_commit 模式帶來的幻讀和資料丟失問題,故障切換時資料一致性將得到提升。因為從庫沒有寫入成功的話主庫也不會提交事務。並且在 commit 之前等待從庫 ACK,還可以堆積事務,有利於 group commit 組提交,有利於提升效能。

但這樣也會有個問題,假設主庫在儲存引擎提交之前掛了,那麼很明顯這個事務是不成功的,但由於對應的 Binlog 已經做了 Sync 操作,從庫已經收到了這些 Binlog,並且執行成功,相當於在從庫上多了資料(從庫上有該資料而主庫沒有),也算是有問題的,但多了資料一般不算嚴重的問題。它能保證的是不丟資料,多了資料總比丟資料要好。

一主多從

如果從庫承擔了大量查詢請求,那麼從庫上的查詢操作將耗費大量的 CPU 資源,從而影響了同步速度,造成主從延遲。那麼我們可以多接幾個從庫,讓這些從庫來共同分擔讀的壓力。

簡而言之,就是加機器,方法簡單粗暴,但也會帶來一定成本。

強制走主庫方案

如果某些操作對資料的實時性要求比較苛刻,需要反映實時最新的資料,比如說涉及金錢的金融類系統、線上實時系統、又或者是寫入之後馬上又讀的業務,這時我們就得放棄讀寫分離,讓此類的讀請求也走主庫,這就不存延遲問題了。

當然這也失去了讀寫分離帶給我們的效能提升,需要適當取捨。

並行複製

一般 MySQL 主從複製有三個執行緒參與,都是單執行緒:Binlog Dump 執行緒、IO 執行緒、SQL 執行緒。複製出現延遲一般出在兩個地方:

1、SQL 執行緒忙不過來(主要原因);

2、網路抖動導致 IO 執行緒複製延遲(次要原因)。

日誌在備庫上的執行,就是備庫上 SQL 執行緒執行中繼日誌(relay log)更新資料的邏輯。

在 MySQL 5.6 版本之前,MySQL 只支援單執行緒複製,由此在主庫併發高、TPS 高時就會出現嚴重的主備延遲問題。從 MySQL 5.6 開始有了多個 SQL 執行緒的概念,可以併發還原資料,即並行複製技術。這可以很好的解決 MySQL 主從延遲問題。

從單執行緒複製到最新版本的多執行緒複製,中間的演化經歷了好幾個版本。其實說到底,所有的多執行緒複製機制,都是要把只有一個執行緒的 sql_thread,拆成多個執行緒,也就是都符合下面的這個多執行緒模型:

coordinator 就是原來的 sql_thread,不過現在它不再直接更新資料了,只負責讀取中轉日誌和分發事務。真正更新日誌的,變成了 worker 執行緒。而 worker 執行緒的個數,就是由引數 slave_parallel_workers 決定的。

由於 worker 執行緒是併發執行的,為了保證事務的隔離性以及不會出現更新覆蓋問題,coordinator 在分發的時候,需要滿足以下這兩個基本要求:

更新同一行的兩個事務,必須被分發到同一個 worker 中(避免更新覆蓋)。

同一個事務不能被拆開,必須放到同一個 worker 中(保證事務隔離性)。

各個版本的多執行緒複製,都遵循了這兩條基本原則。

以下是按表分發策略和按行分發策略,可以幫助理解 MySQL 官方版本並行複製策略的迭代:

1、按表分發策略:如果兩個事務更新不同的表,它們就可以並行。因為資料是儲存在表裡的,所以按表分發,可以保證兩個 worker 不會更新同一行。

按表分發的方案,在多個表負載均勻的場景裡應用效果很好,但缺點是:如果碰到熱點表,比如所有的更新事務都會涉及到某一個表的時候,所有事務都會被分配到同一個 worker 中,就變成單執行緒複製了。

2、按行分發策略:如果兩個事務沒有更新相同的行,則它們在備庫上可以並行。顯然,這個模式要求 binlog 格式必須是 row。

按行並行複製的方案解決了熱點表的問題,並行度更高,但缺點是:相比於按表並行分發策略,按行並行策略在決定執行緒分發的時候,需要消耗更多的計算資源。

MySQL 5.6 版本的並行複製策略
MySQL 5.6 版本,支援了並行複製,只是支援的粒度是按庫並行(基於 Schema)。

其核心思想是:不同 schema 下的表併發提交時的資料不會相互影響,即從庫可以對 relay log 中不同的 schema各分配一個類似 SQL 執行緒功能的執行緒,來重放 relay log 中主庫已經提交的事務,保持資料與主庫一致。

如果在主庫上有多個 DB,使用這個策略對於從庫複製的速度可以有比較大的提升。但通常情況下都是單庫多表,那基於庫的併發也就沒有什麼作用,根本無法並行重放,所以這個策略用得並不多。

MySQL 5.7 的並行複製策略

MySQL 5.7 引入了基於組提交的並行複製,引數 slave_parallel_workers 設定並行執行緒數,由引數 slave-parallel-type 來控制並行複製策略:

配置為 DATABASE,表示使用 MySQL 5.6 版本的按庫並行策略;

配置為 LOGICAL_CLOCK,表示使用基於組提交的並行複製策略;

利用 binlog 的組提交 (group commit) 機制,可以得出一個組提交的事務都是可以並行執行的,原因是:能夠在同一組裡提交的事務,一定不會修改同一行(由於 MySQL 的鎖機制),因為事務已經透過鎖衝突的檢驗了

基於組提交的並行複製具體流程如下

1、在一組裡面一起提交的事務,有一個相同的 commit_id,下一組就是 commit_id+1;commit_id 直接寫到 binlog 裡面;

2、傳到備庫應用的時候,相同 commit_id 的事務分發到多個 worker 執行;

3、這一組全部執行完成後,coordinator 再去取下一批執行。

所有處於 prepare 和 commit 狀態的事務都是可以在備庫上並行執行的。

binlog 的組提交的兩個有關引數:

binlog_group_commit_sync_delay 引數,表示延遲多少微秒後才呼叫 fsync 刷盤;

binlog_group_commit_sync_no_delay_count 引數,表示累積多少次以後才呼叫 fsync。

這兩個引數是用於故意拉長 binlog 從 write 到 fsync 的時間,以此減少 binlog 的寫盤次數。在 MySQL 5.7 的並行複製策略裡,它們可以用來製造更多的“同時處於 prepare 階段的事務”。可以考慮調整這兩個引數值,來達到提升備庫複製併發度的目的。

相關文章