1. 程式人生 > >MYSQL 5.7 並行複製實現原理與調優

MYSQL 5.7 並行複製實現原理與調優

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_Replication_Architecture

在上圖的紅色框框部分就是實現並行複製的關鍵所在。在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型別,如:

1 2 3 4 5 6 7 8 9 10 11 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工具,使用者就能發現組提交的內部資訊:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [email protected]:~# 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