MySQL並行複製(MTS)原理(完整版)

来源:https://www.cnblogs.com/konggg/archive/2022/06/09/16359474.html
-Advertisement-
Play Games

在MySQL 5.7版本,官方稱為enhanced multi-threaded slave(簡稱MTS),複製延遲問題已經得到了極大的改進,可以說在MySQL 5.7版本後,複製延遲問題永不存在。 5.7的MTS本身就是:master基於組提交(group commit)來實現的併發事務分組,再由 ...


目錄

  • 在MySQL 5.7版本,官方稱為enhanced multi-threaded slave(簡稱MTS),複製延遲問題已經得到了極大的改進,可以說在MySQL 5.7版本後,複製延遲問題永不存在。

  • 5.7的MTS本身就是:master基於組提交(group commit)來實現的併發事務分組,再由slave通過SQL thread將一個組提交內的事務分發到各worker線程,實現並行應用。

MySQL 5.6並行複製架構

MySQL 5.7並行複製原理

MySQL 5.6基於庫的並行複製出來後,基本無人問津,在沉寂了一段時間之後,MySQL 5.7出來了,它的並行複製以一種全新的姿態出現在了DBA面前。

MySQL 5.7才可稱為真正的並行複製,這其中最為主要的原因就是slave伺服器的回放與master是一致的,即master伺服器上是怎麼並行執行的,那麼slave上就怎樣進行並行回放。不再有庫的並行複製限制,對於二進位日誌格式也無特殊的要求(基於庫的並行複製也沒有要求)。

從MySQL官方來看,其並行複製的原本計劃是支持表級的並行複製和行級的並行複製,行級的並行複製通過解析ROW格式的二進位日誌的方式來完成,WL#4648。但是最終出現給小伙伴的確是在開發計劃中稱為:MTS(Prepared transactions slave parallel applier),可見:WL#6314。該並行複製的思想最早是由MariaDB的Kristain提出,並已在MariaDB 10中出現,相信很多選擇MariaDB的小伙伴最為看重的功能之一就是並行複製。MTS實現了事務的並行,從某種程度來說也實現了行的並行(事務對行處理)。

下麵來看看MySQL 5.7中的並行複製究竟是如何實現的?

order commit (group commit) -> logical clock ->> MTS

Master

組提交(group commit)

組提交(group commit):通過對事務進行分組,優化減少了生成二進位日誌所需的操作數。當事務同時提交時,它們將在單個操作中寫入到二進位日誌中。如果事務能同時提交成功,那麼它們就不會共用任何鎖,這意味著它們沒有衝突,因此可以在Slave上並行執行。所以通過在主機上的二進位日誌中添加組提交信息,這些Slave可以並行地安全地運行事務。

首先,MySQL 5.7的並行複製基於一個前提,即所有已經處於prepare階段的事務,都是可以並行提交的。這些當然也可以在從庫中並行提交,因為處理這個階段的事務,都是沒有衝突的,該獲取的資源都已經獲取了。反過來說,如果有衝突,則後來的會等已經獲取資源的事務完成之後才能繼續,故而不會進入prepare階段。這是一種新的並行複製思路,完全擺脫了原來一直致力於為了防止衝突而做的分發演算法,等待策略等複雜的而又效率底下的工作。

MySQL 5.7並行複製的思想一言以蔽之:一個組提交(group commit)的事務都是可以並行回放,因為這些事務都已進入到事務的prepare階段,則說明事務之間沒有任何衝突(否則就不可能提交)。

根據以上描述,這裡的重點是——

  1. 如何來定義哪些事務是處於prepare階段的?
  2. 在生成的Binlog內容中該如何告訴Slave哪些事務是可以並行複製的?

——為了相容MySQL 5.6基於庫的並行複製,5.7引入了新的變數slave-parallel-type,其可以配置的值有:

  1. DATABASE(預設值,基於庫的並行複製方式)
  2. LOGICAL_CLOCK(基於組提交的並行複製方式)

支持並行複製的GTID

那麼如何知道事務是否在同一組中?原版的MySQL並沒有提供這樣的信息。

在MySQL 5.7版本中,其設計方式是將組提交的信息存放在GTID中。

那麼如果參數gtid_mode設置為OFF,用戶沒有開啟GTID功能呢?

MySQL 5.7又引入了稱之為Anonymous_Gtid(ANONYMOUS_GTID_LOG_EVENT)的二進位日誌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          |                                              |
| 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,而這個Anonymous_Gtid事件中就存在著組提交的信息。反之,如果開啟了GTID後,就不會存在這個Anonymous_Gtid了,從而組提交信息就記錄在非匿名GTID事件中。

  • PREVIOUS_GTIDS_LOG_EVENT

    用於表示上一個binlog最後一個gitd的位置,每個binlog只有一個,當沒有開啟GTID時此事件為空。

  • GTID_LOG_EVENT

    • 當開啟GTID時,每一個操作語句(DML/DDL)執行前就會添加一個GTID事件,記錄當前全局事務ID。

    • 同時在MySQL 5.7版本中,組提交信息也存放在GTID事件中,有兩個關鍵欄位last_committed,sequence_number就是用來標識組提交信息的。

    • 在InnoDB中有一個全局計數器(global counter),在每一次存儲引擎提交之前,計數器值就會增加。在事務進入prepare階段之前,全局計數器的當前值會被儲存在事務中,這個值稱為此事務的commit-parent(也就是last_committed)。

slave

LOGICAL_CLOCK(由order commit實現),實現的group commit目的

然而,通過上述的SHOW BINLOG EVENTS,我們並沒有發現有關組提交的任何信息。但是通過mysqlbinlog工具,就能發現組提交的內部信息——

$ 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
...

上述的last_committed和sequence_number代表的就是所謂的LOGICAL_CLOCK。

可以發現MySQL 5.7二進位日誌較之原來的二進位日誌內容多了last_committed和sequence_number。

  • last_committed表示事務提交時上次事務提交的編號,事務在進入prepare階段時會將上次事務的sequence_number記錄為自己的last_committed,如果事務具有相同的last_committed,表示這些事務都在一組內,可以進行並行的回放。
    • 例如上述last_committed為0的事務有6個,表示組提交時提交了6個事務,而這6個事務在slave是可以進行並行回放的。
  • sequence_number是順序增長的,每個事務對應一個序列號,當事務完成committed時便會得到這個sequence_number。

另外,還有一個細節,下一個事務組的last_committed和上一個事務的sequence_number是相等的。這也很容易理解,因為事物是順序提交的,這麼理解起來並不奇怪。本組的 sequence_number最小值肯定大於last_committed。(這一塊描述不嚴謹,在5.7後續版本中,官方優化了slave進行並行apply的規則,但是這裡為了便於理解,不做修改,理解這個思路後閱讀後面基於鎖的並行規則也很容易。)

這兩個值的有效作用域都在文件內,只要換一個binlog文件(flush binary logs),這兩個值就都會從0開始計數。

MySQL是如何做到將這些事務分組的?

還有一個重要的技術問題:MySQL是如何做到將這些事務分組的?

要搞清楚這個問題,首先需要瞭解一下MySQL事務提交方式。

1. 事務兩階段提交

事務的提交主要分為兩個主要步驟:

  1. 準備階段(Storage Engine(InnoDB)Transaction Prepare Phase)

    此時SQL已經成功執行,並生成xid信息及redo和undo的記憶體日誌。然後調用prepare方法完成第一階段,papare方法實際上什麼也沒做,將事務狀態設為TRX_PREPARED,並將redo log刷磁碟。

  2. 提交階段(Storage Engine(InnoDB)Commit Phase)

    1. 記錄Binlog日誌。

      如果事務涉及的所有存儲引擎的prepare都執行成功,則調用TC_LOG_BINLOG::log_xid方法將SQL語句寫到binlog。

      (write()將binary log記憶體日誌數據寫入文件系統緩存,fsync()將binary log文件系統緩存日誌數據永久寫入磁碟)。

      此時,事務已經鐵定要提交了。否則,調用ha_rollback_trans方法回滾事務,而SQL語句實際上也不會寫到binlog。

    2. 告訴引擎做commit。

      最後,調用引擎的commit完成事務的提交。會清除undo信息,刷redo日誌,將事務設為TRX_NOT_STARTED狀態。

(不好理解這段就看上面的圖解好了。)

2. Order Commit:是LOGICAL_CLOCK並行複製的基礎

關於MySQL是如何提交的,內部使用ordered_commit函數來處理的。先看它的邏輯圖,如下:

從圖中可以看到,只要事務提交(調用ordered_commit),就都會先加入隊列中。

提交有三個步驟,包括FLUSH、SYNC及COMMIT,相應地也有三個隊列。

  • 首先要加入的是FLUSH隊列:

    1. 如果某個事務加入時,隊列還是空的,則這個事務就擔任隊長,來代表其他事務執行提交操作。
    2. 而在其他事務繼續加入時,就會發現此時隊列已經不為空了,那麼這些事務就會在隊列中等待隊長幫它們完成提交操作。在上圖中,事務2-6都是這種坐享其成之輩,事務1就是隊長了。
    3. 這裡需要註意一點,不是說隊長會一直等待要提交的事務不停地加入,而是有一個時限,這個時限就是從隊長加入開始,到它去處理隊列的時間——等待binlog_group_commit_sync_delay毫秒,便進行一次組提交,如果在等待事件範圍內提前達到binlog_group_commit_sync_no_delay_count事務個數時,也會直接進行一次組提交。
    • 只要隊長將這個隊列中的事務取出,其他事務就可以加入這個等待隊列了。第一個加入的還是隊長,但此時必須要等待。因為此時有事務正在做FLUSH,做完FLUSH之後,其他的隊長才能帶著隊員做FLUSH。
    • 在同一時刻,只能有一個組在做FLUSH。這就是上圖中所示的等待事務組2和等待事務組3,此時隊長會按照順序依次做FLUSH。
    • 做FLUSH的過程中,有一些重要的事務需要去做,如下:
      1. 要保證順序必須是提交加入到隊列的順序。

      2. 如果有新的事務提交,此時隊列為空,則可以加入到FLUSH隊列中。不過,因為此時FLUSH臨界區正在被占用,所以新事務組必須要等待。

      3. 給每個事務分配sequence_number,如果是第一個事務,則將這個組的last_committed設置為sequence_number-1.

      4. 將帶著last_committed與sequence_number的GTID事件FLUSH到Binlog文件中。

      5. 將當前事務所產生的Binlog內容FLUSH到Binlog文件中。

        這樣,一個事務的FLUSH就完成了。接下來,依次做完組內所有事務的FLUSH。然後做SYNC。
        做完FLUSH之後,FLUSH臨界區會空閑出來,此時在等待這個臨界區的組就可以做FLUSH操作了。

  • SYNC隊列
    如果SYNC的臨界區是空的,則直接做SYNC操作,而如果已經有事務組在做,則必須要等待。

  • COMMIT隊列
    到COMMIT時,實際做的是存儲引擎提交,參數binlog_order_commits會影響提交行為。

    • 如果設置為ON,那麼此時提交就變為串列操作了,就以隊列的順序為提交順序。
    • 如果設置為OFF,提交就不會在這裡進行,而會在每個事務(包括隊長和隊員)做finish_commit(FINISH)時各自做存儲引擎的提交操作。
      • 組內每個事務做finish_commit是在隊長完成COMMIT工序之後進行,到步驟DONE時,便會喚醒每個等待提交完成的事務,告訴他們可以繼續了,那麼每個事務就會去做finish_commit。
      • 而後,隊長自己再去做finish_commit。這樣,一個組的事務就都按部就班地提交完成了。

現在應該搞明白關於order commit的原理了,而這也是LOGICAL_CLOCK並行複製的基礎

因為order commit使得所有的事務分了組,並且有了序列號,從庫拿到這些信息之後,就可以根據序號放心大膽地做分發了。

探索:binlog_group_commit_sync_delay 、binlog_group_commit_sync_no_delay_count對group commit的影響:

從時間上說,從隊長開始入隊,到取隊列中的所有事務出來,這之間的時間是非常非常小的,所以在這段時間內其實不會有多少個事務。

只有在壓力很大,提交的事務非常多的時候,才會提高併發度(組內事務數變大)。

不過這個問題也可以解釋得通,主庫壓力小的時候,從庫何必要那麼大的併發度呢?只有主庫壓力大的時候,從庫才會延遲。

這種情況下也可以通過調整主伺服器上的參數binlog_group_commit_sync_delaybinlog_group_commit_sync_no_delay_count

  • binlog_group_commit_sync_delay表示事務延遲提交多少時間來加大整個組提交的事務數量,從而減少進行磁碟刷盤sync的次數,單位為1/1000000秒,最大值1000000也就是1秒;
  • binlog_group_commit_sync_no_delay_count表示組提交的事務數量湊齊多少此值時就跳出等待,然後提交事務,而無需等待binlog_group_commit_sync_delay的延遲時間;但是binlog_group_commit_sync_no_delay_count也不會超過binlog_group_commit_sync_delay設置。

兩個參數都是為了增加主伺服器組提交的事務比例,從而增大從機MTS的並行度。

事務group commit,logical clock(order commit)示意圖:

假設當前環境配置參數:

binlog_group_commit_sync_delay = 1000
binlog_group_commit_sync_no_delay_count = 5

圖中:
T0->T1->..->T6,每一個區間表示一個binlog_group_commit_sync_delay = 1000 時間範圍,紅虛線將該時間範圍5等分。
其中,T0為session1 - session10 十個會話同時開啟事務的時間節點。
tn-m,為session-n在當前位置進行了第m次提交動作。

  • 當時間進行到T1時,達到binlog_group_commit_sync_delay = 1000 的delay時間限制,本次group commit內容為:(不考慮隊長順序)

    t1-1,last_committed=0, sequence_number=1
    t2-1,last_committed=0, sequence_number=2
    t3-1,last_committed=0, sequence_number=3
    t5-1,last_committed=0, sequence_number=4
    
  • 當時間進行到T2時,再一次達到binlog_group_commit_sync_delay = 1000 的delay時間限制,本次group commit內容為:(不考慮隊長順序)

    t2-2,last_committed=4, sequence_number=5
    t4-1,last_committed=4, sequence_number=6
    t7-1,last_committed=4, sequence_number=7
    t8-1,last_committed=4, sequence_number=8
    
  • 當時間進行到T3時,再一次達到binlog_group_commit_sync_delay = 1000 的delay時間限制,本次group commit內容為:(不考慮隊長順序)

    t3-2,last_committed=8, sequence_number=9
    t8-2,last_committed=8, sequence_number=10
    t9-1,last_committed=8, sequence_number=11
    
  • 當時間進行到T3a時,儘管未達到binlog_group_commit_sync_delay = 1000 的delay時間限制,但是已經發生5次提交,達到binlog_group_commit_sync_no_delay_count = 5計數上限,將立即進行組提交,本次group commit內容為:(不考慮隊長順序)

    t1-2,last_committed=11, sequence_number=12
    t2-3,last_committed=11, sequence_number=13
    t6-1,last_committed=11, sequence_number=14
    t7-2,last_committed=11, sequence_number=15
    t8-3,last_committed=11, sequence_number=16
    
  • 當時間進行到T4a時,儘管未達到binlog_group_commit_sync_delay = 1000 的delay時間限制,但是已經發生5次提交,達到binlog_group_commit_sync_no_delay_count = 5計數上限,將立即進行組提交,本次group commit內容為:(不考慮隊長順序)

    t1-3,last_committed=16, sequence_number=17
    t2-4,last_committed=16, sequence_number=18
    t4-2,last_committed=16, sequence_number=19
    t5-2,last_committed=16, sequence_number=20
    t8-4,last_committed=16, sequence_number=21
    
  • 一個彩蛋。當t10-1事務提交後,將會立即執行組提交,為什麼?

    • 因為T4a時間點進行組提交後,delay 1000(5格時間單位)的提交時間點剛好在t10-1事務提交發生的同一時間。
    • 也因為T4a時間點進行組提交後,截至t10-1事務提交,count剛好達到計數上限——5。
      本次group commit內容為:(不考慮隊長順序)
      t3-3,last_committed=21, sequence_number=22
      t6-2,last_committed=21, sequence_number=23
      t7-3,last_committed=21, sequence_number=24
      t9-2,last_committed=21, sequence_number=25
      t10-1,last_committed=21, sequence_number=26
      

從庫多線程複製分發原理

知道了order commit原理之後,現在很容易可以想到在從庫端是如何分發的:

從庫以事務為單位做APPLY的,每個事務有一個GTID事件,因此都有一個last_committed及sequence_number值。

1. 基於last_committed分發原理如下:

因為last_committed值的記錄方式是:master將上一組最後一個sequence_number記錄為下一組的last_committed,因此本組的sequence_number最小值肯定大於last_committed,下一組的last_committed肯定大於前一組sequence_number的最小值(因為等於sequence_number最大值)

  1. sql thread拿到一個新事務,取出該事務的last_committed及sequence_number值。
  2. 將已經執行的事務的sequence_number的最小值(low water mark,lwm),與取出事務的last_committed值進行比較。(本組的sequence_number最小值肯定大於last_committed
  3. 如果取出事務的last_committed小於已經執行的sequence(lwm),說明取出事務與當前執行組為同組,無需等待,直接由sql thread 分配事務到空閑worker線程。
  4. SQL線程通過統計,找到一個空閑的worker線程,如果沒有空閑,則SQL線程轉入等待狀態,直到找到一個空閑worker線程為止。將當前事務打包,交給選定的worker,之後worker線程會去APPLY這個事務,此時的SQL線程就會處理下一個事務。
  5. 如果取出事務的last_committed大於等於已經執行的lwm,說明取出事務與當前不為一組,取出事務為新組,需等待。
  6. 等待lwm增長,當已經執行的sequence(lwm)等於取出事務的last_committed時,說明前一組已經執行完成。sql thread 開始將取出事務的last_committed組事務分發給worker線程進行並行apply。

原理示意參考:

  • 事務示意參考:

    t3-3,last_committed=21, sequence_number=22
    t6-2,last_committed=21, sequence_number=23
    t7-3,last_committed=21, sequence_number=24
    t9-2,last_committed=21, sequence_number=25
    t10-1,last_committed=21, sequence_number=26
    new,last_committed=26, sequence_number=27
    
  • 假設此時sql thread 剛剛將事務t3-3分發給worker線程:

    • sql thread拿出事務(t6-2)的last_committed和sequence_number(21,23),

      • 如果拿出事務的last_committed(21)小於當前已經執行的sequence_number的最小值(22),說明拿出的事務與正在執行的事務是同組,無需等待。
    • sql thread拿出事務(t7-3)的last_committed和sequence_number(21,24),

      • 如果拿出事務的last_committed(21)小於當前已經執行的sequence_number的最小值(22),說明拿出的事務與正在執行的事務是同組,無需等待。

      ……

    • sql thread拿出事務(new)的last_committed和sequence_number(26,27),

      • 如果拿出事務的last_committed(26)大於等於當前已經執行的sequence_number的最小值(22),說明拿出的事務是新的一組,拿出的事務需等待。
      • 當sql thread判斷已經執行的sequence_number 等於拿出事務的last_committed時,說明可以開始新一組的apply了。
  • 當事務(t10-1)執行後,已經執行的sequence_number(26) = 拿出事務的last_committed(26),前一組已經執行完成,sql thread 開始將last_committed=26的組事務分發給worker線程進行並行apply。

Commit-Parent-Based Scheme簡介(WL#7165)

  • 在master上,有一個全局計數器(global counter)。在每一次存儲引擎完成提交之前,計數器值就會增加
  • 在master上,在事務進入prepare階段之前,全局計數器的當前值會被儲存在事務中。這個值稱為此事務的commit-parent(last_committed)。
  • 在master上,commit-parent會在事務的開頭被儲存在binlog中。
  • 在slave上,如果兩個事務有同一個commit-parent,他們就可以並行被執行。

此commit-parent就是我們在binlog中看到的last_committed。如果commit-parent相同,即last_committed相同,則被視為同一組,可以並行回放。

基於last_committed分發(Commit-Parent-Based Scheme)存在的問題

一句話:Commit-Parent-Based Scheme會降低複製的並行程度

解釋一下圖:

  • 水平虛線表示事務按時間順序往後走。

  • P表示事務在進入prepare階段之前讀到的commit-parent值的那個時間點(last_committed)。可以簡單的視為加鎖時間點。

  • C表示事務增加了全局計數器(global counter)的值的那個時間點(sequence)。可以簡單的視為釋放鎖的時間點

  • P對應的commit-parent(last_commited)是取自所有已經執行完的事務的最大的C對應的sequence_number。

    • 舉例來說:
      • Trx4的P對應的commit-parent(last_commited)取自所有已經執行完的事務的最大的C對應的sequence_number=1,也就是Trx1的C對應的sequence_number。因為這個時候Trx1已經執行完,但是Trx2還未執行完
      • Trx5的P對應的commit-parent(last_commited)取自所有已經執行完的事務的最大的C對應的sequence_number=2,也就是Trx2的C對應的sequence_number;
      • Trx6的P對應的commit-parent(last_commited)取自所有已經執行完的事務的最大的C對應的sequence_number=2,也就是Trx2的C對應的sequence_number。所以Trx5和Trx6具有相同的commit-parent(last_commited),在進行回放的時候,Trx5和Trx6可以並行回放。
  • 由圖可見:

    • Trx5 和Trx6可以併發執行,因為他們的commit-parent是相同的,都是由Trx2設定的。
    • Trx4和Trx5不能併發執行,
    • Trx6和Trx7也不能併發執行。

    可以註意到,在同一時段,Trx4和Trx5、Trx6和Trx7分別持有他們各自的鎖,事務互不衝突。如果在slave上併發執行,也是不會有問題的

  • 根據以上例子,可以得知:

    • 在基於last_committed規則下,Trx4、Trx5和Trx6在同一時間持有各自的鎖,但Trx4無法併發執行,因為Trx4取到的laste_committed和後兩者不同。
    • Trx6和Trx7在同一時間持有各自的鎖,但Trx7無法併發執行,原因一樣。

    實際上,Trx4是可以和Trx5、Trx6並行執行,Trx6可以和Trx7並行執行。如果能實現這個,那麼並行複製的效果會更好。

    所以官方對並行複製的機製做了改進,提出了一種新的並行複製的方式:Lock-Based Scheme。# 5.7開始基於lock interval的並行規則(WL#7165)

說明:上面的步驟是以事務為單位介紹的,其實實際處理中還是一個事件一個事件地分發。如果一個事務已經選定了worker,而新的event還在那個事務中,則直接交給那個worker處理即可。

從上面的分發原理來看,同時執行的都是具有相同last_committed值的事務,不同的只是後面的需要等前面做完了才能執行,這樣的執行方式有點如下圖所示:

可以看出,事務都是隨機分配到了worker線程中,但是執行的話,必須是一行一行地執行。一行事務個數越多,並行度越高,也說明主庫瞬時壓力越大

2. MySQL 5.7開始基於lock interval的並行規則(WL#7165)

實現:如果兩個事務在同一時間持有各自的鎖,就可以併發執行。

對前一個原理需要補充為:

因為last_committed值的記錄方式是master將上一組最後一個sequence_number記錄為下一組的last_committedmaster將MySQL全局變數global.max_committed_transaction(所有已經結束lock interval的事務的最大的sequence_number)記錄為下一組的last_committed,因此本組的sequence_number最小值肯定大於last_committed,下一組的last_committed肯定大於前一組sequence_number的最小值(因為等於sequence_number最大值)

# 根據基於鎖特性,實際上是與本組第一個Prepare存在時間間隙的上一組C的那個事務的sequence,也就是說,如果前一組的後幾個事務與當前組的前幾個事務存在lock interval重疊,那麼前一組的這幾個事務再向前一個事務的sequence才是當前組的last_committed

Lock-Based Scheme簡介(WL#7165)

首先,定義了一個稱為lock interval的概念,含義:一個事務持有鎖的時間間隔

  • 當存儲引擎提交,第一把鎖釋放,lock interval結束。
  • 當最後一把鎖獲取,lock interval開始。

假定:最後一把鎖獲取是在binlog_prepare階段。

假設有兩個事務:Trx1、Trx2。Trx1先於Trx2。那麼,

  • 當且僅當Trx1、Trx2的lock interval有重疊,則可以並行執行。

    Tx0 ,Tx1在同一個時間區間(lock interval),都持有各自的鎖。

    也就是說,同一時間這兩個事務持有各自的鎖沒有衝突,因此這兩個事務可以並行apply。lock interval重疊可以並行。

  • 換言之,如果Trx1的lock interval結束點與Trx2的lock interval開始點存在間隙,則不能並行執行

    Tx0 ,Tx1的兩個事務prepare到committed發生時間不重疊(lock interval不重疊),無法確定同一時間這兩個事務持有各自的鎖是否存在衝突

    因此這兩個事務不可以並行apply。

  • MySQL會獲取全局變數global.max_committed_transaction,含義:所有已經結束lock interval的事務的最大的sequence_number

  • L表示lock interval的開始點

    • 對於L(lock interval的開始點),MySQL會把global.max_committed_timestamp分配給一個變數,並取名叫transaction.last_committed
  • C表示lock interval的結束

    • 對於C(lock interval的結束點),MySQL會給每個事務分配一個邏輯時間戳(logical timestamp),命名為:transaction.sequence_number

transaction.sequence_numbertransaction.last_committed這兩個時間戳都會存放在binlog中。

  • 根據以上分析,我們可以得出在slave上執行事務的條件:

如果所有正在執行的事務的最小的sequence_number大於一個事務的transaction.last_committed,那麼這個事務就可以併發執行。(這句話太繞,不用強求,看下麵土味理解好了)

土味理解Lock-Based Scheme

在這先拋開writeset,不要混淆了,理解了這個會有助於理解writeset原理。

  • 基於commit parent的方式, 事務的last_committed肯定等於前一組最後一個事務的sequence number。

  • 但是在基於lock interval方式時,不是這樣了,事務的last_committed不一定等於前一組最後一個事務的sequence number了,而是等於所有已經結束lock interval的事務的最大的sequence_number

  • 舉例說明:

    Lock-Based Scheme例子

…
t1,last_committed=0, sequence_number=3
t2,last_committed=3, sequence_number=4
t3,last_committed=3, sequence_number=5
t4,last_committed=3, sequence_number=6
t5,last_committed=3, sequence_number=7
t6,last_committed=6, sequence_number=8
t7,last_committed=6, sequence_number=9
t8,last_committed=9, sequence_number=10
  • 事務t1,last_committed=0,sequence_number=3。第一個work線程會接手這個事務並開始工作。
  • 事務t2,last_committed=3, sequence_number=4。直到事務t1完成,事務t2才能開始。因為last_committed=3不小於正在執行執行事務的sequence_number=3。所以這兩個事務只能串列。
  • 雖然前2個事務可能會被分配到不同的work線程,但實際上他們是串列的,就像單線程複製那樣。
  • 當sequence_number=3的事務完成,last_committed=3的三個事務就可以併發執行。
    t3,last_committed=3, sequence_number=5
    t4,last_committed=3, sequence_number=6
    t5,last_committed=3, sequence_number=7
    
  • 一旦前兩個(t3,t4)執行完成,下麵這兩個可以開始執行:
    last_committed=6 sequence_number=8 last_committed=6 sequence_number=9
    

因為last_committed=6小於正在執行執行事務的sequence_number=7,可以並行。

  • 也就是說,當t5,last_committed=3, sequence_number=7正在執行的時候,sequence_number=8和sequence_number=9這兩個也可以併發執行。

  • 這三個事務的結束沒有前後順序的限制。

  • 因為這三個事務的lock interval有重疊,因此可以併發執行,所以事務之間並不會相互影響

  • 等到前面的事務均完成之後,下麵這個事務才可以進行:

    t8,last_committed=9, sequence_number=10
    
  • 看完更暈了?沒關係,不用糾結,看下麵

  • 首先說明,圖中事務Tx1作為參考事務,忽略它,它的意義就是為Tx2事務提供一個last_committed。

  • Tx2--Tx5為第一組,Tx6~Tx7為第二組,用底色做了區分。

  • 可以看到:

    1. 事務Tx2~Tx5都存在lock interval重疊,這4個事務可以並行apply,因此這4個事務在一個組。
    2. 事務Tx6因為和事務Tx4沒有發生lock interval重疊,因此事務Tx6無法和Tx4並行,也就無法成為前一組的成員,只能自己成立新組。
    3. 第一組的最後一個事務Tx5和第二組的事務Tx6、Tx7三個事務存在lock interval重疊,雖然跨組,但是這3個事務是滿足並行邏輯,可以並行進行的。
    4. 第二組的last值=6,並不是第一組最後一個事務的sequence_number=7。(為什麼?↓)
  • 實際上第二組的last_committed值是取自於這個規則:

  1. 幾個關鍵的時間點:

    1. 第二組第一個事務開始prepare的時間點稱為A點(last_committed)。
    2. A點發生時,第一組中所有已經結束lock interval的事務的最大的sequence_number稱為B點。
    3. 第一組最後一個事務Tx5的commit時間稱為C點(sequence_number)
  2. 在A點發生prepare時,B點和A點之間存在間隙(就是說,事務tx4和事務tx6不存在鎖重疊),Tx4,Tx6無法並行,因此A點進行prepare的事務Tx6成為了新組的事務。

  3. A點取當時所有已經結束lock interval的事務的最大的sequence_number作為自己的last_committed。Tx6的last_committed=6。

總結一句話就是:last_committed值取自於前一組中,與本組事務不存在lock interval重疊的最後一個事務的sequence number

  • 結論:

    • 事務之間存在lock interval重疊便可以並行apply,但是只要任意兩個事務之間存在gap(事務lock interval不重疊)便會導致分組。
    • 分組只是避免鎖衝突,並不意味著無法並行(不管有沒有鎖衝突,只要事務不重疊就悲觀認為存在衝突,拒絕並行)。
    • 能否並行的只根據一個情況判斷,就是事務之間lock interval重疊。因此即使事務在不同的組中,只要存在lock interval重疊,就可能會並行apply。

MySQL 5.7並行複製測試

下圖顯示了開啟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這個文件的更新將會大幅提升,資源的競爭也會變大。

  • slave_parallel_workers

    若將slave_parallel_workers設置為0,則MySQL 5.7退化為原單線程複製,但將slave_parallel_workers設置為1,則SQL線程功能轉化為coordinator線程,但是只有1個worker線程進行回放,也是單線程複製。然而,這兩種性能卻又有一些的區別,因為多了一次coordinator線程的轉發,因此slave_parallel_workers=1的性能反而比0還要差,測試下還有20%左右的性能下降,如下圖所示:

這裡其中引入了另一個問題,如果主機上的負載不大,那麼組提交的效率就不高,很有可能發生每組提交的事務數量僅有1個,那麼在從機的回放時,雖然開啟了並行複製,但會出現性能反而比原先的單線程還要差的現象,即延遲反而增大了。聰明的小伙伴們,有想過對這個進行優化嗎?

  • slave_preserve_commit_order

    MySQL 5.7後的MTS可以實現更小粒度的並行複製,但需要將slave_parallel_type設置為LOGICAL_CLOCK,但僅僅設置為LOGICAL_CLOCK也會存在問題,因為此時在slave上應用事務的順序是無序的,和relay log中記錄的事務順序不一樣,這樣數據一致性是無法保證的,為了保證事務是按照relay log中記錄的順序來回放,就需要開啟參數slave_preserve_commit_order

    開啟該參數後,執行線程將一直等待, 直到提交之前所有的事務。當sql thread正在等待其他worker提交其事務時, 其狀態為等待前面的事務提交。

    所以雖然MySQL 5.7添加MTS後,雖然slave可以並行應用relay log,但commit部分仍然是順序提交,其中可能會有等待的情況。

    當開啟slave_preserve_commit_order參數後,slave_parallel_type只能是LOGICAL_CLOCK,如果你有使用級聯複製,那LOGICAL_CLOCK可能會使離master越遠的slave並行性越差

    但是經過測試,這個參數在MySQL 5.7.18中設置之後,也無法保證slave上事務提交的順序與relay log一致。

    在MySQL 5.7.19設置後,slave上事務的提交順序與relay log中一致(所以生產要想使用MTS特性,版本大於等於MySQL 5.7.19才是安全的)

說了這麼多,要開啟enhanced multi-threaded slave其實很簡單,只需根據如下設置:

# slave;
slave-parallel-type=LOGICAL_CLOCK
slave-parallel-workers=16
slave_pending_jobs_size_max = 2147483648
slave_preserve_commit_order=1
master_info_repository=TABLE
relay_log_info_repository=TABLE
relay_log_recovery=ON

在使用了MTS後,複製的監控依舊可以通過SHOW SLAVE STATUS\G,但是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)

通過replication_applier_status_by_worker可以看到worker進程的工作情況:

mysql> select * from replication_applier_status_by_worker;
+--------------+-----------+-----------+---------------+--------------------------------------------+-------------------+--------------------+----------------------+
| CHANNEL_NAME | WORKER_ID | THREAD_ID | SERVICE_STATE | LAST_SEEN_TRANSACTION           | LAST_ERROR_NUMBER | LAST_ERROR_MESSAGE | LAST_ERROR_TIMESTAMP |
+--------------+-----------+-----------+---------------+--------------------------------------------+-------------------+--------------------+----------------------+
|       |     1 |    32 | ON      | 0d8513d8-00a4-11e6-a510-f4ce46861268:96604 |         0 |          | 0000-00-00 00:00:00 |
|       |     2 |    33 | ON      | 0d8513d8-00a4-11e6-a510-f4ce46861268:97760 |         0 |          | 0000-00-00 00:00:00 |
+--------------+-----------+-----------+---------------+--------------------------------------------+-------------------+--------------------+----------------------+
2 rows in set (0.00 sec)

那麼怎樣知道從機MTS的並行程度又是一個難度不小。簡單的一種方法(薑總給出的),可以使用performance_schema庫來觀察,比如下麵這條SQL可以統計每個Worker Thread執行的事務數量,在此基礎上再做一個聚合分析就可得出每個MTS的並行度:

SELECT thread_id,count_star FROM performance_schema.events_transactions_summary_by_thread_by_event_name
WHERE thread_id IN (SELECT thread_id FROM performance_schema.replication_applier_status_by_worker);

如果線程並行度太高,不夠平均,其實並行效果並不會好,可以試著優化。這種場景下,可以通過調整主伺服器上的參數binlog_group_commit_sync_delay、binlog_group_commit_sync_no_delay_count。前者表示延遲多少時間提交事務,後者表示組提交事務湊齊多少個事務再一起提交。總體來說,都是為了增加主伺服器組提交的事務比例,從而增大從機MTS的並行度。

雖然MySQL 5.7推出的Enhanced Multi-Threaded Slave在一定程度上解決了困擾MySQL長達數十年的複製延遲問題。然而,目前MTS機制基於組提交實現,簡單來說在主上是怎樣並行執行的,從伺服器上就怎麼回放。這裡存在一個可能,即若主伺服器的並行度不夠,則從機的並行機制效果就會大打折扣。MySQL 8.0最新的基於writeset的MTS才是最終的解決之道。即兩個事務,只要更新的記錄沒有重疊(overlap),則在從機上就可並行執行,無需在一個組,即使主伺服器單線程執行,從伺服器依然可以並行回放。相信這是最完美的解決之道,MTS的最終形態。

最後,如果MySQL 5.7要使用MTS功能,必須使用最新版本,最少升級到5.7.19版本,修複了很多Bug。

參考信息
http://www.ywnds.com/?p=3894
運維內參書籍
薑總的公眾號文章
http://mysql.taobao.org/monthly/2017/12/03/
https://mp.weixin.qq.com/s/XbWMdVTl9qz1nSwL3l56XQ

個個原創文章

歡迎討論
https://www.cnblogs.com/konggg/
歡迎轉載收藏,轉載請註明來源,謝謝支持!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • LNMP是Linux+Nginx+Mysql+PHP所構建的一個動態開發環鏡 我這裡使用的系統是華為的OpenEnler系統,使用了Nginx1.12版本、Mysql8和PHP7.4 如果有出錯的地方可能是作者沒做到位,見諒 安裝依賴包並安裝nginx: # mount /dev/cdrom /mn ...
  • 本文講解Linux伺服器 Ubuntu20.04 設置靜態IP方法。 ...
  • 英文原文: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cpuacct.html CPU Accounting Controller CPU統計控制器(CPU Accounting Controller)用來分組使用cgr ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡巴巴開源鏡像站 前言 最近,學習了胡老師的《ROS入門21講》,在Ubuntu18.04上安裝ROS過程中遇到了一些問題,解決這些問題耗費了大半天,故通過本文進行詳細安裝介紹,以便其他學者在安裝這塊少花時間,把更多的精力放在研究上。 一、環境配置 我的環境:虛 ...
  • 作用:命令行多視窗顯示;命令行程式與本機脫離 1 安裝tmux (1)redhat、centos系統 yum install tmux (2)ubuntu系統 apt-get install tmux 2 使用tmux (1)啟動 首先,我們使用遠程登錄工具,登錄到遠程伺服器上,然後執行下麵的命令: ...
  • 虛擬機關鍵配置名詞解釋 虛擬⽹絡編輯器 橋接模式 可以訪問互聯⽹,配置的地址信息和物理主機⽹段地址信息相同,容易造成地址衝突 NAT模式 可以訪問互聯⽹,配置的地址信息和物理主機⽹段地址信息不同,造成不了地址衝突 僅主機模式 不可以訪問互聯⽹,獲取地址主要⽤於虛擬主機之間溝通,但不能訪問外部⽹絡 網 ...
  • 亂序問題 在業務編寫 FlinkSQL 時, 非常常見的就是亂序相關問題, 在出現問題時,非常難以排查,且無法穩定復現,這樣無論是業務方,還是平臺方,都處於一種非常尷尬的地步。 亂序問題 在業務編寫 FlinkSQL 時, 非常常見的就是亂序相關問題, 在出現問題時,非常難以排查,且無法穩定復現,這 ...
  • 本文給大家介紹資料庫中用來管理數據更新的重要概念——SQL 事務。簡單來講,事務就是需要在同一個處理單元中執行的一系列更新處理的集合。 本文重點 事務是需要在同一個處理單元中執行的一系列更新處理的集合。通過使用事務,可以對資料庫中的數據更新處理的提交和取消進行管理。 事務處理的終止指令包括 COMM ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...