<b>前言</b>
與oracle 不同,mysql 的主庫與備庫的同步是通過 binlog 實作的,而redo日志隻做為mysql 執行個體的crash recovery使用。mysql在4.x 的時候放棄redo 的同步政策而引入 binlog的同步,一個重要原因是為了相容其它非事務存儲引擎,否則主備同步是沒有辦法進行的。
redo 日志同步屬于實體同步方法,簡單直接,将修改的實體部分傳送到備庫執行,主備共用一緻的 lsn,隻要保證 lsn 相同即可,同一時刻,隻能主庫或備庫一方接受寫請求; binlog的同步方法屬于邏輯複制,分為statement 或 row 模式,其中statement記錄的是sql語句,row 模式記錄的是修改之前的記錄與修改之後的記錄,即前鏡像與後鏡像;備庫通過binlog dump 協定拉取binlog,然後在備庫執行。如果拉取的binlog是sql語句,備庫會走和主庫相同的邏輯,如果是row 格式,則會調用存儲引擎來執行相應的修改。
本文簡單說明5.5到5.7的主備複制性能改進過程。
replication improvement (from 5.5 to 5.7)
<b>(1) 5.5 中,binlog的同步是由兩個線程執行的</b>
io_thread: 根據binlog dump協定從主庫拉取binlog, 并将binlog轉存到本地的relaylog;
sql_thread: 讀取relaylog,根據位點的先後順序執行binlog event,進而将主庫的修改同步到備庫,達到主備一緻的效果; 由于在主庫的更新是由多個用戶端執行的,是以當壓力達到一定的程度時,備庫單線程執行主庫的binlog跟不上主庫執行的速度,進而會産生延遲造成備庫不可用,這也是分庫的原因之一,其sql線程的執行堆棧如下:
(2) <b>5.6 中,引入了多線程模式,在多線程模式下,其線程結構如下</b>
io_thread: 同5.5
coordinator_thread: 負責讀取 relay log,将讀取的binlog event以事務為機關分發到各個 worker thread 進行執行,并在必要時執行binlog event(description_format_log_event, rotate_log_event 等)。
worker_thread: 執行配置設定到的binlog event,各個線程之間互不影響;
<b>多線程原理</b>
sql_thread 的分發原理是依據目前事務所操作的資料庫名稱來進行分發,如果事務是跨資料庫行為的,則需要等待已配置設定的該資料庫的事務全部執行完畢,才會繼續分發,其配置設定行為的僞碼可以簡單的描述如下:
<b>需要注意的細節</b>
記憶體的配置設定與釋放。relay thread 每讀取一個log_event, 則需要 malloc 一定的記憶體,在work線程執行完後,則需要free掉;
資料庫名 與 worker 線程的綁定資訊在一個hash表中進行維護,hash表以entry為機關,entry中記錄目前entry所代表的資料庫名,有多少個事務相關的已被分發,執行這些事務的worker thread等資訊;
維護一個綁定資訊的array , 在分發事務的時候,更新綁定資訊,增加相應 entry->usage, 在執行完一個事務的時候,則需要減少相應的entry->usage;
slave worker 資訊的維護,即每個 worker thread執行了哪些事務,執行到的位點是在哪,延遲是如何計算的,如果執行出錯,mts_recovery_group 又是如何恢複的;
配置設定線程是以資料庫名進行分發的,當一個執行個體中隻有一個資料庫的時候,不會對性能有提高,相反,由于增加額外的操作,性能還會有一點回退;
臨時表的處理,臨時表是和entry綁定在一起的,在執行的時候将entry的臨時表挂在執行線程thd下面,但沒有固化,如果在臨時表操作期間,備庫crash,則重新開機後備庫會有錯誤;
<b>總體上說,5.6 的并行複制打破了5.5 單線程的複制的行為,隻是在單庫下用處不大,并且5.6的并行複制的改動引入了一些重量級的bug</b>
(3) 5.7中,并行複制的實作添加了另外一種并行的方式,即主庫在 ordered_commit中的第二階段的時候,将同一批commit的 binlog 打上一個相同的seqno标簽,同一時間戳的事務在備庫是可以同時執行的,是以大大簡化了并行複制的邏輯,并打破了相同 db 不能并行執行的限制。備庫在執行時,具有同一seqno的事務在備庫可以并行的執行,互不幹擾,也不需要綁定資訊,後一批seqno的事務需要等待前一批相同seqno的事務執行完後才可以執行。