探索MySQL的高效數(shù)據(jù)同步:并行復(fù)制原理
在聊 MySQL 的并行原理之前,我們要了解什么會有這個概念,以及這個要解決什么問題。這就不得不提到主從延遲這個概念了。
什么是數(shù)據(jù)庫的主從延遲,如何解決?
數(shù)據(jù)庫的主從延遲指的是主服務(wù)器(Master)和從服務(wù)器(Slave)之間數(shù)據(jù)同步的時間差或延遲。導(dǎo)致主從延遲的常見原因包括:
- 網(wǎng)絡(luò)延遲:主節(jié)點和從節(jié)點之間的網(wǎng)絡(luò)延遲導(dǎo)致數(shù)據(jù)復(fù)制延遲。
- 從節(jié)點性能問題:從服務(wù)器性能不足,如 CPU、內(nèi)存、磁盤資源不足,無法及時處理復(fù)制事件,導(dǎo)致延遲增加。
- 復(fù)制線程不足:從節(jié)點的復(fù)制線程不足或配置不合理,無法有效地處理接收到的復(fù)制事件,造成數(shù)據(jù)回放速度較慢,進而導(dǎo)致主從延遲。
解決主從延遲可以考慮以下幾個方面:
- 優(yōu)化網(wǎng)絡(luò):確保主從節(jié)點之間的網(wǎng)絡(luò)連接穩(wěn)定,盡量在同一城市或同一數(shù)據(jù)中心部署,以減小網(wǎng)絡(luò)延遲。
- 提升從服務(wù)器性能:增加從服務(wù)器的硬件資源,例如增加 CPU 核數(shù)、內(nèi)存容量和改善磁盤性能,以提升從服務(wù)器處理復(fù)制事件的能力。
- 并行復(fù)制:利用 MySQL 提供的并行復(fù)制能力,同時處理多個復(fù)制事件,提高復(fù)制效率,從而降低主從延遲。
這些措施可以有助于減少主從延遲,提升數(shù)據(jù)庫復(fù)制的效率和穩(wěn)定性。
上面提到了并行復(fù)制這個概念,接下來我們就簡單聊聊并行復(fù)制。
在 MySQL 的主從復(fù)制中,我們已經(jīng)介紹過其基本原理。在復(fù)制過程中,主庫的 binlog 會不斷地同步到從庫,而從庫則通過一個 SQL 線程不斷地拉取并重放這些 SQL 語句。然而,當日志內(nèi)容過多時,單個線程的執(zhí)行會產(chǎn)生延遲,導(dǎo)致主從延遲。
為了解決這一問題,MySQL 提供了并行復(fù)制的方案。在多個版本中,MySQL 相繼推出了多種并行復(fù)制的方案:
- MySQL 5.6 引入了基于庫級別的并行復(fù)制。
- MySQL 5.7 推出了基于組提交的并行復(fù)制。
- MySQL 8.0 推出了基于 WRITESET 的并行復(fù)制。
庫級別并行復(fù)制
在 MySQL 5.6 中,并行復(fù)制是基于 Schema(即基于庫)的,可以配置多個庫并行進行復(fù)制。每個庫都可以有自己的復(fù)制線程,并行處理來自不同庫的寫入,從而提升并行復(fù)制的性能和效率。
然而,實際上大多數(shù)業(yè)務(wù)都是單庫的,這使得這一方案在推出后并未獲得廣大開發(fā)者和 DBA 的認可,認為其實用性不足。
組提交的的并行復(fù)制
由于 MySQL 5.6 的并行復(fù)制飽受詬病,MySQL 5.7 推出了基于組提交的并行復(fù)制,這才是真正意義上的并行復(fù)制,即著名的 MTS(Enhanced Multi-Threaded Slave)??蓞⒖脊俜轿臋n:
https://dev.mysql.com/blog-archive/multi-threaded-replication-performance-in-mysql-5-7/
這里先簡單了解下組提交,然后繼續(xù)往下看。
在介紹組提交時我們提到,一個組中的多個事務(wù)在處于 Prepare 階段之后,才會被優(yōu)化成組提交。這意味著,如果多個事務(wù)能夠在同一個組內(nèi)提交,這些事務(wù)在鎖上一定是沒有沖突的。
binlog_transaction_dependency_tracking = WRITESET # COMMIT_ORDER
transaction_write_set_extraction = XXHASH64
換句話說,這幾個事務(wù)修改的記錄一定不是同一行,因此它們之間才能互不影響地同時進入 Prepare 階段,并進行組提交。
那么,沒有沖突的多條 SQL,是不是就可以在主備同步過程中,在備庫上并行執(zhí)行回放呢?
答案是肯定的。因為一個組中的多條 SQL 之間互不影響,無論先執(zhí)行哪一條,結(jié)果都是相同的。
因此,Slave 可以使用多個 SQL 線程來并行執(zhí)行一個組提交中的多條 SQL,從而提高效率,減少主從延遲。
基于 WRITESET 的并行復(fù)制
前面的組提交大大提升了主從復(fù)制的效率,但它有一個特點,即依賴于主庫的并行度。如果主庫的并發(fā)度較高,才可以進行組提交,從而利用組提交的并行復(fù)制優(yōu)化。
如果主庫的 SQL 執(zhí)行并不頻繁,時間間隔可能會超過組提交的參數(shù)閾值,就不會進行組提交,這樣在復(fù)制時就無法使用并行復(fù)制。
為了解決這個問題,MySQL 8.0 引入了基于 WriteSet 的并行復(fù)制。在這種情況下,即使主庫是串行提交的事務(wù),只要這些事務(wù)之間互不沖突,備庫就可以并行回放,從而提升復(fù)制效率。
開啟 WRITESET:
binlog_transaction_dependency_tracking = WRITESET # COMMIT_ORDER
transaction_write_set_extraction = XXHASH64
實際上,WriteSet 是一個集合,使用的是 C++ STL 中的 set 容器。
std::set<uint64> write_set_unique;
集合中的每一個元素都是哈希值,這個哈希值與 transaction_write_set_extraction 參數(shù)指定的算法有關(guān)(可選值為 OFF、MURMUR32、XXHASH64,默認值為 XXHASH64),其來源是行數(shù)據(jù)的主鍵和唯一鍵。
WriteSet 通過檢測兩個事務(wù)是否更新了相同的記錄來判斷事務(wù)能否并行回放,因此需要在運行時保存已提交的事務(wù)信息以記錄歷史事務(wù)更新了哪些行。在進行更新時,需要進行沖突檢測,將新更新的記錄計算出的哈希值與 WriteSet 進行比較,如果不存在沖突,則認為是不沖突的,這樣就可以共用同一個 last_committed。
last_committed 指的是該事務(wù)提交時,上一個事務(wù)提交的編號。
就這樣,能夠確保同一個 write_set 中的變更都是不沖突的,因此可以通過多個線程并行地回放 SQL。