MySQL 並行複製演進及 MySQL 8.0 中基於 WriteSet 的優化

 

 

MySQL 8.0 能夠說是MySQL發展歷史上里程碑式的一個版本,包括了多個重大更新,目前 Generally Available 版本已經已經發布,正式版本即將發佈,在此將介紹8.0版本中引入的一個重要的新特性————基於 WriteSet 的並行複製方案,此方案號稱是完全解決困擾MySQL運維人員多年的複製延遲問題。html

說到並行複製,這裏簡單的回顧一下各個版本的MySQL複製的演進,以幫助理解8.0版本中對並行複製MTS的優化。mysql

 

MySQL 主從複製模型

 

一切都要從MySQL的主從複製模型開始提及,下圖是最經典的MySQL主從複製模型架構圖算法

MySQL 複製模型sql

 

MySQL的主從架構依賴於 MySQL Binlog 功能, Master節點上產生Binlog並將Binlog寫入到Binlog文件中。Slave節點上啓動兩個線程:一個IO線程,從MySQL上撈取Binlog日誌並寫入到本地的RelayLog日誌;另外一個SQL線程,不斷的從RelayLog日誌中讀取日誌,並解析執行。這樣經過在主機和從機上增長几個文件的順序讀寫操做,就能夠保證全部在主機上執行過的SQL語句都在從機上一摸同樣的執行過一遍。而複製延遲,指的就是一個事務在Master執行完成之後,要多久之後才能在Slave上執行完成。數據庫

因爲對Binlog文件以及RelayLog文件的讀寫均爲順序操做,在生產環境中,Slave上的IO線程對Binlog文件的Dump操做是不多產生延遲的。 實際上,從MySQL 5.5 開始,MySQL官方提供了半同步複製插件,每一個事務的Binlog須要保證傳輸到Slave寫入 RelayLog 後才能提交,這種架構在主從之間提供了數據完整性,保證了主機在發生故障後從機能夠擁有完整的數據副本。所以,複製延遲一般發生在SQL線程執行的過程當中。從架構圖上能夠看到,最先的主從複製模型中,只有一個線程負責執行 Relaylog,也就是說全部在主機上的操做,在從機上是串行回放的。 這就帶來一個問題,若是主上寫入壓力比較大,那麼從上的回放速度頗有可能會一直跟不上主。(除此以外,MySQL的架構決定了Binlog只有在Commit階段纔會寫入Binlog文件並Dump給從機,這也致使主從事務必然有執行延遲,這個問題在大事務中體現的特別明顯,不過這個問題就不在本文的討論範圍內了)架構

既然主從延遲的問題是單線程回放RelayLog太慢,那麼減小主從延遲的方案天然就是提升從機上回放RelayLog 的並行度。併發

 

5.6中的並行複製————Schema級別的並行複製

 

MySQL官方在5.6中引入了一個比較簡單並行複製方案,其架構以下:(圖片來自姜承堯老師的博客)app

 

紅色框部分爲並行回放的關鍵,5.6中若開啓並行回放的功能,便會啓動多個WorkThread ,而原來負責回放的SQLThread會轉變成Coordinator角色,負責判斷事務可否並行執行並分發給WorkThread。運維

若是事務分別屬於不一樣的Schema,而且不是DDL語句且沒有跨Schema操做,那麼就能夠並行回放,不然須要等全部Worker線程執行完成後再執行當前日誌中的內容。性能

這種並行回放是Schema級別的並行,若是實例上有多個Schema將會所以收益,而若是實例上只有一個Schema,那麼事務將沒法並行回放,並且還會因多了分發的操做致使效率略微降低。而在實際應用中,單庫多表纔是更常見的狀況。

 

5.7中的並行複製————基於Group Commit 的並行複製

 

雖然5.6中的並行複製在大多數應用場景中對回放速度的提高不大,可是該架構卻成爲了後來MySQL並行複製的基礎——既在Slave上並行回放RelayLog,SQL線程負責判斷可否並行回放,並分配給Work線程回放。

5.6 中引入Group Commit技術,這是爲了解決事務提交的時候須要fsync致使併發性不夠而引入的。簡單來講,就是因爲事務提交時必須將Binlog寫入到磁盤上而調用fsync,這是一個代價比較高的操做,事務併發提交的狀況下,每一個事務各自獲取日誌鎖並進行fsync會致使事務實際上以串行的方式寫入Binlog文件,這樣就大大下降了事務提交的併發程度。5.6中採用的Group Commit技術將事務的提交階段分紅了 Flush, Sync, Commit 三個階段,每一個階段維護一個隊列,而且由該隊列中第一個線程負責執行該步驟,這樣實際上就達到了一次能夠將一批事務的Binlog fsync到磁盤的目的,這樣的一批同時提交的事務稱爲同一個Group的事務。

Group Commit 雖然是屬於並行提交的技術,可是卻意外的解決了從機上事務並行回放的一個難題————既如何判斷哪些事務能夠並行回放。若是一批事務是同時Commit的,那麼這些事務必然不會互斥的持有鎖,也不會有執行上的相互依賴,所以這些事務必然能夠並行的回放。

所以MySQL 5.7 中引入了新的並行回放類型, 由參數 slave_parallel_type決定,默認值DATABASE將會採用5.6版本中的SCHEMA級別的並行回放,設置爲 LOGICAL_LOCK 則會採用基於GroupCommit的並行回放,同一個Group內的事務將會在Slave上並行回放。

爲了標記事務所屬的組,MySQL 5.7 版本在產生 Binlog 日誌時會有兩個特殊的值記錄在Binlog Event中, last_committedsequence_number , 其中 last_committed 指的是該事務提交時,上一個事務提交的編號,sequence_number 是事務提交的序列號,在一個Binlog文件內單調遞增。若是兩個事務的 last_committed 值一致,這兩個事務就是在一個組內提交的。

 

root@localhost:~# mysqlbinlog mysql-bin.0000006 | grep last_committed
#150520 14:23:11 server id 88 end_log_pos 259 CRC32 0x4ead9ad6 GTID last_committed=0 sequence_number=1
#150520 14:23:11 server id 88 end_log_pos 1483 CRC32 0xdf94bc85 GTID last_committed=0 sequence_number=2
#150520 14:23:11 server id 88 end_log_pos 2708 CRC32 0x0914697b GTID last_committed=0 sequence_number=3
#150520 14:23:11 server id 88 end_log_pos 3934 CRC32 0xd9cb4a43 GTID last_committed=0 sequence_number=4
#150520 14:23:11 server id 88 end_log_pos 5159 CRC32 0x06a6f531 GTID last_committed=0 sequence_number=5
#150520 14:23:11 server id 88 end_log_pos 6386 CRC32 0xd6cae930 GTID last_committed=0 sequence_number=6
#150520 14:23:11 server id 88 end_log_pos 7610 CRC32 0xa1ea531c GTID last_committed=6 sequence_number=7
#150520 14:23:11 server id 88 end_log_pos 8834 CRC32 0x96864e6b GTID last_committed=6 sequence_number=8
#150520 14:23:11 server id 88 end_log_pos 10057 CRC32 0x2de1ae55 GTID last_committed=6 sequence_number=9
#150520 14:23:11 server id 88 end_log_pos 11280 CRC32 0x5eb13091 GTID last_committed=6 sequence_number=10
#150520 14:23:11 server id 88 end_log_pos 12504 CRC32 0x16721011 GTID last_committed=6 sequence_number=11
#150520 14:23:11 server id 88 end_log_pos 13727 CRC32 0xe2210ab6 GTID last_committed=6 sequence_number=12
#150520 14:23:11 server id 88 end_log_pos 14952 CRC32 0xf41181d3 GTID last_committed=12 sequence_number=13

 

 

如上 binlog 文件中, sequence_number 1-6 的事務 last_committed 都是0 ,所以屬於同一個組,能夠在slave上並行回放, 7-12的last_committed 都是6,也屬於同一個組,所以能夠並行回放。

5.7 中引入的基於Logical_Lock極大的提升了在主機併發壓力比較大的狀況下,從機上的回放速度。基本上作到了主機上如何提交的,在從機上如何回放。

 

MySQL MGR 中的 WriteSet

 

在5.7中基於邏輯時鐘 Logical_Clock 的並行複製任然有不盡人意的地方,必須是在主上並行提交的事務才能在從上並行回放,若是主上併發壓力不大,那麼就沒法享受到並行複製帶來的好處。5.7 中引入了 binlog_group_commit_sync_delaybinlog_group_commit_sync_no_delay_count 兩個參數,經過讓Binlog在執行 fsync 前等待一小會來提升Master上組提交的比率。可是不管如何,從上並行回放的速度仍是取決於主上並行提交的狀況。

MySQL 8.0中引入了一種新的機制來判斷事務可否並行回放,經過檢測事務在運行過程當中是否存在寫衝突來決定從機上的回放順序,這使得從機上的併發程度再也不依賴於主機。

事實上,該機制在 MySQL 5.7.20 版本中就已經悄悄的應用了。5.7.20版本引入了一個重要的特性: Group Replication,經過Paxso協議在多個MySQL節點間分發binlog,使得一個事務必須在集羣內大多數節點(N/2+1)上提交成功才能提交。爲了支持多主寫入,MySQL MRG 在Binlog分發節點完成後,經過一個 Certify 階段來決定Binlog中的事務是否寫入RelayLog 中。這個過程當中,Certify 階段採用的就是WriteSet的方式驗證事務之間是否存在衝突,同時,在寫入RelayLog 時會將沒有衝突的事務的 last_committed 值設置爲相同的值。

好比在5.7.20中,進行以下操做:

 

> -- create a group replication cluster.
> STOP GROUP_REPLICATION; START GROUP_REPLICATION;
Query OK, 0 rows affected (9.10 sec)

> -- All the next commands on the primary member of the group:
> CREATE DATABASE test_ws_mgr ;
Query OK, 1 row affected (0.01 sec)

> CREATE TABLE test_ws_mgr.test ( id int primary key auto_increment, str varchar(64) not null );
Query OK, 1 row affected (0.01 sec)

> INSERT INTO test_ws_mgr.test(`str`) VALUES ("a");
Query OK, 1 row affected (0.01 sec)

> INSERT INTO test_ws_mgr.test(`str`) VALUES ("b");
Query OK, 1 row affected (0.01 sec)

> INSERT INTO test_ws_mgr.test(`str`) VALUES ("c");
Query OK, 1 row affected (0.01 sec)

 

 

以上代碼在一個MGR 集羣中建立了一個數據庫和一個INNODB表,並插入了三條記錄。這個時候,如何查詢Primary 節點上的Binlog,可能會獲得以下結果

 

# mysqlbinlog mysql-bin.N | grep last_ | sed -e 's/server id.*last/[...] last/' -e 's/.rbr_only.*/ [...]/'
#180106 19:31:59 [...] last_committed=0 sequence_number=1 [...] -- CREATE DB
#180106 19:32:02 [...] last_committed=1 sequence_number=2 [...] -- CREATE TB
#180106 19:32:05 [...] last_committed=2 sequence_number=3 [...] -- INSERT a
#180106 19:32:08 [...] last_committed=3 sequence_number=4 [...] -- INSERT b
#180106 19:32:11 [...] last_committed=4 sequence_number=5 [...] -- INSERT c

 

 

能夠看到,因爲是在一個Session中,這些操做按着串行的順序有着不一樣的 last_committed , 正常狀況下,這些BinlogEvent應該在從機上一樣以串行的方式回放。咱們看一下在MGR集羣中的relaylog 狀況。

 

# mysqlbinlog mysql-relay.N | grep -e last_ | sed -e 's/server id.*last/[...] last/' -e 's/.rbr_only.*/ [...]/'
#180106 19:31:36 [...] last_committed=0 sequence_number=0 [...]
#180106 19:31:36 [...] last_committed=1 sequence_number=2 [...] -- CREATE DB
#180106 19:31:36 [...] last_committed=2 sequence_number=3 [...] -- CREATE TB
#180106 19:31:36 [...] last_committed=3 sequence_number=4 [...] -- INSERT a
#180106 19:31:36 [...] last_committed=3 sequence_number=5 [...] -- INSERT b
#180106 19:31:36 [...] last_committed=3 sequence_number=6 [...] -- INSERT c

 

 

有趣的是,在 Secondary 節點的 RelayLog 中, 這些事務有着相同的 last_committed 值,也就是說這些事務在MGR集羣中,回放的時候能夠以並行的方式回放。

MGR中,使用的正是 WriteSet 技術檢測不一樣事務之間是否存在寫衝突,並重規劃了事務的並行回放,這一技術在8.0中被移到了Binlog生成階段,並採用到了主從複製的架構中。

 

MySQL 8.0 中的並行複製

 

說了這麼多,終於講到 MySQL 8.0 , 經過以上描述,讀者應該對 MySQL 8.0 中並行複製的優化的原理有了一個大體的輪廓。經過基於 WriteSet 的衝突檢測,在主機上產生 Binlog 的時候,再也不基於組提交,而是基於事務自己的更新衝突來肯定並行關係。

 

相關的 MySQL 參數

 

MySQL 8.0 中引入參數 binlog_transaction_depandency_tracking 用於控制如何決定事務的依賴關係。該值有三個選項:默認的 COMMIT_ORDERE 表示繼續使用5.7中的基於組提交的方式決定事務的依賴關係;WRITESET 表示使用寫集合來決定事務的依賴關係;還有一個選項 WRITESET_SESSION 表示使用 WriteSet 來決定事務的依賴關係,可是同一個Session內的事務不會有相同的 last_committed 值。

在代碼實現上,MySQL採用一個 vector<uint64> 的變量存儲已經提交的事務的HASH值,全部已經提交的事務的所修改的 主鍵和非空的 UniqueKey 的值通過HASH後與該vector中的值對比,以判斷當前提交的事務是否與已經提交的事務更新了同一行,並以此肯定依賴關係。該向量的大小由參數 binlog_transaction_dependency_history_size 控制,取值範圍爲 1-1000000 ,初始默認值爲 25000。 同時有參數 transaction_write_set_extraction 控制檢測事務依賴關係時採用的HASH算法,有三個取值 OFF| XXHASH64 | MURMUR32, 若是 binlog_transaction_depandency_tracking 取值爲 WRITESET 或 WRITESET_SESSION, 那麼該值取值不能爲OFF,且不能變動。

 

WriteSet 依賴檢測條件

 

WriteSet 是經過檢測兩個事務是否更新了相同的記錄來判斷事務可否並行回放的,所以須要在運行時保存已經提交的事務信息以記錄歷史事務更新了哪些行。記錄歷史事務的參數爲 binlog_transaction_dependency_history_size. 該值越大能夠記錄更多的已經提交的事務信息,不過須要注意的是,這個值並不是指事務大小,而是指追蹤的事務更新信息的數量。在開啓了 WRITESET 或 WRITESET_SESSION 後,MySQL 按如下的方式標識並記錄事務的更新。

  • 若是事務當前更新的行有主鍵(Primary Key),則將 HASH(DB名,TABLE名,KEY名稱,KEY_VALUE1, KEY_VALUE2,.....) 加入到當前事務的 vector write_set 中。

  • 若是事務當前更新的行有非空的惟一鍵 (Unique Key Not NULL), 一樣將 HASH(DB名, TABLE名,KEY名, KEY_VALUE1, ....)加入到當前事務的 write_set 中。

  • 若是事務更新的行有外鍵約束( FOREIGN KEY )且不爲空,則將該 外鍵信息與VALUE 的HASH加到當前事務的 write_set 中

  • 若是事務當前更新的表的主鍵是其餘某個表的外鍵,並設置當前事務 has_related_foreign_key = true

  • 若是事務更新了某一行且沒有任何數據被加入到 write_set 中,則標記當前事務 has_missing_key = true

在執行衝突檢測的時候,先會檢查 has_related_foreign_key 和 has_missing_key , 若是爲true, 則退到 COMMIT_ORDER 模式。不然,會依照事務的 write_set 中的HASH值與已提交的事務的 write_set 進行比對,若是沒有衝突,則當前事務與最後一個已提交的事務共享相同的 last_commited, 不然將從全局已提交的 write_set 中刪除那個衝突的事務以前提交的全部write_set,並退化到 COMMIT_ORDER 計算last_committed 。 每次計算完事務的 last_committed 值之後,檢測當前全局已提交事務的 write_set 是否已經超過了 binlog_transaction_dependency_history_size 設置的值,若是超過,則清空已提交事務的全局 write_set。

從檢測條件上看,該特性依賴於 主鍵和惟一索引,若是事務涉及的表中沒有主鍵且沒有惟一非空索引,那麼將沒法今後特性中得到性能的提高。除此以外,還須要將 Binlog 格式設置爲 Row 格式。

 

性能提高

 

MySQL High Availability 對開啓了WriteSet的複製性能作了測試,這裏直接將測試結果搬運過來,有興趣的能夠直接訪問原博客

測試時經過Sysbench 先在主機上執行100W條事務,而後開啓Slave的複製線程,測試環境在Xeon E5-2699-V3 16核主機上執行,如下是測試結果

 

 

 

能夠看到,在客戶端線程比較少的時候,WRITESET 具備最好的性能,在只有一個鏈接的時候 WRITESET_SESSION 和 COMMIT_ORDER 差異不大。

 

結論

 

從 MySQL Hight Availability 的測試中能夠看到,開啓了基於 WriteSet 的事務依賴後,對Slave上RelayLog回放速度提高顯著。Slave上的 RelayLog 回放速度將再也不依賴於 Master 上提交時的並行程度,使得Slave上能夠發揮其最大的吞吐能力, 這個特性在Slave上覆制中止一段時間後恢復複製時尤爲有效。

這個特性使得 Slave 上可能擁有比 Master 上更大的吞吐量,同時可能在保證事務依賴關係的狀況下,在 Slave 上產生 Master 上沒有產生過的提交場景,事務的提交順序可能會在 Slave 上發生改變。 雖然在5.7 的並行複製中就可能發生這種狀況,不過在8.0中因爲 Slave 上更高的併發能力,會使該場景更加常見。 一般狀況下這不是什麼大問題,不過若是在 Slave 上作基於 Binlog 的增量備份,可能就須要保證在 Slave 上與Master 上一致的提交順序,這種狀況下能夠開啓 slave_preserve_commit_order 這是一個 5.7 就引入的參數,能夠保證 Slave 上並行回放的線程按 RelayLog 中寫入的順序 Commit。

 

參考:

 

http://jfg-mysql.blogspot.jp/2018/01/an-update-on-write-set-parallel-replication-bug-fix-in-mysql-8-0.html

http://jfg-mysql.blogspot.jp/2018/01/write-set-in-mysql-5-7-group-replication.html

https://mysqlhighavailability.com/improving-the-parallel-applier-with-writeset-based-dependency-tracking/

相關文章
相關標籤/搜索