MySQL 5.7并行復制時代
眾所周知,MySQL的復制延遲是一直被詬病的問題之一,然而在Inside君之前的兩篇博客中(1,2)中都已經提到了MySQL 5.7版本已經支持“真正”的并行復制功能,官方稱為為enhanced multi-threaded slave(簡稱MTS),因此復制延遲問題已經得到了極大的改進,甚至在Inside君所在的網易電商應用中已經完全消除了之前延遲長達幾小時的問題。然而,Inside君發現還是有很多小伙伴并不了解這個足以載入史冊的“偉大”的特性,故作分享。總之,5.7版本后,復制延遲問題永不存在。
MySQL 5.6并行復制架構
誠然,MySQL 5.6版本也支持所謂的并行復制,但是其并行只是基于schema的,也就是基于庫的。如果用戶的MySQL數據庫實例中存在多個schema,對于從機復制的速度的確可以有比較大的幫助。MySQL 5.6并行復制的架構如下所示:
在上圖的紅色框框部分就是實現并行復制的關鍵所在。在MySQL 5.6版本之前,Slave服務器上有兩個線程I/O線程和SQL線程。I/O線程負責接收二進制日志(更準確的說是二進制日志的event),SQL線程進行回放二進制日志。如果在MySQL 5.6版本開啟并行復制功能,那么SQL線程就變為了coordinator線程,coordinator線程主要負責以前兩部分的內容:
若判斷可以并行執行,那么選擇worker線程執行事務的二進制日志
若判斷不可以并行執行,如該操作是DDL,亦或者是事務跨schema操作,則等待所有的worker線程執行完成之后,再執行當前的日志 這意味著coordinator線程并不是僅將日志發送給worker線程,自己也可以回放日志,但是所有可以并行的操作交付由worker線程完成。coordinator線程與worker是典型的生產者與消費者模型。
上述機制實現了基于schema的并行復制存在兩個問題,首先是crash safe功能不好做,因為可能之后執行的事務由于并行復制的關系先完成執行,那么當發生crash的時候,這部分的處理邏輯是比較復雜的。從代碼上看,5.6這里引入了Low-Water-Mark標記來解決該問題,從設計上看(WL#5569),其是希望借助于日志的冪等性來解決該問題,不過5.6的二進制日志回放還不能實現冪等性。另一個最為關鍵的問題是這樣設計的并行復制效果并不高,如果用戶實例僅有一個庫,那么就無法實現并行回放,甚至性能會比原來的單線程更差。而單庫多表是比多庫多表更為常見的一種情形。
MySQL 5.7并行復制原理
MySQL 5.7基于組提交的并行復制
MySQL 5.7才可稱為真正的并行復制,這其中最為主要的原因就是slave服務器的回放與主機是一致的即master服務器上是怎么并行執行的slave上就怎樣進行并行回放。不再有庫的并行復制限制,對于二進制日志格式也無特殊的要求(基于庫的并行復制也沒有要求)。
從MySQL官方來看,其并行復制的原本計劃是支持表級的并行復制和行級的并行復制,行級的并行復制通過解析ROW格式的二進制日志的方式來完成,WL#4648。但是最終出現給小伙伴的確是在開發計劃中稱為:MTS: Prepared transactions slave parallel applier,可見:WL#6314。該并行復制的思想最早是由MariaDB的Kristain提出,并已在MariaDB 10中出現,相信很多選擇MariaDB的小伙伴最為看重的功能之一就是并行復制。
MySQL 5.7并行復制的思想簡單易懂,一言以蔽之:一個組提交的事務都是可以并行回放,因為這些事務都已進入到事務的prepare階段,則說明事務之間沒有任何沖突(否則就不可能提交)。
為了兼容MySQL 5.6基于庫的并行復制,5.7引入了新的變量slave-parallel-type,其可以配置的值有:
DATABASE:默認值,基于庫的并行復制方式
LOGICAL_CLOCK:基于組提交的并行復制方式
支持并行復制的GTID
如何知道事務是否在一組中,又是一個問題,因為原版的MySQL并沒有提供這樣的信息。在MySQL 5.7版本中,其設計方式是將組提交的信息存放在GTID中。那么如果用戶沒有開啟GTID功能,即將參數gtid_mode設置為OFF呢?故MySQL 5.7又引入了稱之為Anonymous_Gtid的二進制日志event類型,如:
mysql>?SHOW?BINLOG?EVENTS?in?'mysql-bin.000006'; ?+------------------+-----+----------------+-----------+-------------+-----------------------------------------------+ ?|?Log_name?|?Pos?|?Event_type?|?Server_id?|?End_log_pos?|?Info?| ?+------------------+-----+----------------+-----------+-------------+-----------------------------------------------+ ?|?mysql-bin.000006?|?4?|?Format_desc?|?88?|?123?|?Server?ver:?5.7.7-rc-debug-log,?Binlog?ver:?4?| ?|?mysql-bin.000006?|?123?|?Previous_gtids?|?88?|?194?|?f11232f7-ff07-11e4-8fbb-00ff55e152c6:1-2?| ?|?mysql-bin.000006?|?194?|?Anonymous_Gtid?|?88?|?259?|?SET?@@SESSION.GTID_NEXT=?'ANONYMOUS'?| ?|?mysql-bin.000006?|?259?|?Query?|?88?|?330?|?BEGIN?| ?|?mysql-bin.000006?|?330?|?Table_map?|?88?|?373?|?table_id:?108?(aaa.t)?| ?|?mysql-bin.000006?|?373?|?Write_rows?|?88?|?413?|?table_id:?108?flags:?STMT_END_F?| ?.....
這意味著在MySQL 5.7版本中即使不開啟GTID,每個事務開始前也是會存在一個Anonymous_Gtid,而這GTID中就存在著組提交的信息。
LOGICAL_CLOCK
然而,通過上述的SHOW BINLOG EVENTS,我們并沒有發現有關組提交的任何信息。但是通過mysqlbinlog工具,用戶就能發現組提交的內部信息:
root@localhost:~#?mysqlbinlog?mysql-bin.0000006?|?grep?last_committed ?#150520?1411?server?id?88?end_log_pos?259?CRC32?0x4ead9ad6?GTID?last_committed=0?sequence_number=1 ?#150520?1411?server?id?88?end_log_pos?1483?CRC32?0xdf94bc85?GTID?last_committed=0?sequence_number=2 ?#150520?1411?server?id?88?end_log_pos?2708?CRC32?0x0914697b?GTID?last_committed=0?sequence_number=3 ?#150520?1411?server?id?88?end_log_pos?3934?CRC32?0xd9cb4a43?GTID?last_committed=0?sequence_number=4 ?#150520?1411?server?id?88?end_log_pos?5159?CRC32?0x06a6f531?GTID?last_committed=0?sequence_number=5 ?#150520?1411?server?id?88?end_log_pos?6386?CRC32?0xd6cae930?GTID?last_committed=0?sequence_number=6 ?#150520?1411?server?id?88?end_log_pos?7610?CRC32?0xa1ea531c?GTID?last_committed=6?sequence_number=7 ?...
可以發現較之原來的二進制日志內容多了last_committed和sequence_number,last_committed表示事務提交的時候,上次事務提交的編號,如果事務具有相同的last_committed,表示這些事務都在一組內,可以進行并行的回放。例如上述last_committed為0的事務有6個,表示組提交時提交了6個事務,而這6個事務在從機是可以進行并行回放的。
上述的last_committed和sequence_number代表的就是所謂的LOGICAL_CLOCK。先來看源碼中對于LOGICAL_CLOCK的定義:
class?Logical_clock ?{ ?private: ?int64?state; ?/* ?Offset?is?subtracted?from?the?actual?"absolute?time"?value?at ?logging?a?replication?event.?That?is?the?event?holds?logical ?timestamps?in?the?"relative"?format.?They?are?meaningful?only?in ?the?context?of?the?current?binlog. ?The?member?is?updated?(incremented)?per?binary?log?rotation. ?*/ ?int64?offset; ?......
state是一個自增的值,offset在每次二進制日志發生rotate時更新,記錄發生rotate時的state值。其實state和offset記錄的是全局的計數值,而存在二進制日志中的僅是當前文件的相對值。使用LOGICAL_CLOCK的場景如下:
class?MYSQL_BIN_LOG:?public?TC_LOG ?{ ?... ?public: ?/*?Committed?transactions?timestamp?*/ ?Logical_clock?max_committed_transaction; ?/*?"Prepared"?transactions?timestamp?*/ ?Logical_clock?transaction_counter; ?...
可以看到在類MYSQL_BIN_LOG中定義了兩個Logical_clock的變量:
max_c ommitted_transaction:記錄上次組提交時的logical_clock,代表上述mysqlbinlog中的last_committed
transaction_counter:記錄當前組提交中各事務的logcial_clock,代表上述mysqlbinlog中的sequence_number
并行復制測試
下圖顯示了開啟MTS后,slave服務器的QPS。測試的工具是sysbench的單表全update測試,測試結果顯示在16個線程下的性能最好,從機的QPS可以達到25000以上,進一步增加并行執行的線程至32并沒有帶來更高的提升。而原單線程回放的QPS僅在4000左右,可見MySQL 5.7 MTS帶來的性能提升,而由于測試的是單表,所以MySQL 5.6的MTS機制則完全無能為力了。
并行復制配置與調優
master_info_repository
開啟MTS功能后,務必將參數master_info_repostitory設置為TABLE,這樣性能可以有50%~80%的提升。這是因為并行復制開啟后對于元master.info這個文件的更新將會大幅提升,資源的競爭也會變大。在之前InnoSQL的版本中,添加了參數來控制刷新master.info這個文件的頻率,甚至可以不刷新這個文件。因為刷新這個文件是沒有必要的,即根據master-info.log這個文件恢復本身就是不可靠的。在MySQL 5.7中,Inside君推薦將master_info_repository設置為TABLE,來減小這部分的開銷。
slave_parallel_workers
若將slave_parallel_workers設置為0,則MySQL 5.7退化為原單線程復制,但將slave_parallel_workers設置為1,則SQL線程功能轉化為coordinator線程,但是只有1個worker線程進行回放,也是單線程復制。然而,這兩種性能卻又有一些的區別,因為多了一次coordinator線程的轉發,因此slave_parallel_workers=1的性能反而比0還要差,在Inside君的測試下還有20%左右的性能下降,如下圖所示:
這里其中引入了另一個問題,如果主機上的負載不大,那么組提交的效率就不高,很有可能發生每組提交的事務數量僅有1個,那么在從機的回放時,雖然開啟了并行復制,但會出現性能反而比原先的單線程還要差的現象,即延遲反而增大了。聰明的小伙伴們,有想過對這個進行優化嗎?
Enhanced Multi-Threaded Slave配置
說了這么多,要開啟enhanced multi-threaded slave其實很簡單,只需根據如下設置:
#?slave ?slave-parallel-type=LOGICAL_CLOCK ?slave-parallel-workers=16 ?master_info_repository=TABLE ?relay_log_info_repository=TABLE ?relay_log_recovery=ON
并行復制監控 復制的監控依舊可以通過SHOW SLAVE STATUSG,但是MySQL 5.7在performance_schema架構下多了以下這些元數據表,用戶可以更細力度的進行監控:
mysql>?show?tables?like?'replication%'; ?+---------------------------------------------+ ?|?Tables_in_performance_schema?(replication%)?| ?+---------------------------------------------+ ?|?replication_applier_configuration?| ?|?replication_applier_status?| ?|?replication_applier_status_by_coordinator?| ?|?replication_applier_status_by_worker?| ?|?replication_connection_configuration?| ?|?replication_connection_status?| ?|?replication_group_member_stats?| ?|?replication_group_members?| ?+---------------------------------------------+ ?8?rows?in?set?(0.00?sec)
總結
MySQL 5.7推出的Enhanced Multi-Threaded Slave解決了困擾MySQL長達數十年的復制延遲問題,再次提醒一些無知的PostgreSQL用戶,不要停留在之前對于MySQL的印象,物理復制也不一定肯定比邏輯復制有優勢,而MySQL 5.7的MTS已經完全可以解決延遲問題了。
編輯:黃飛
?
評論
查看更多