一、背景
1.1傳統主從複制存在的問題
衆所周知,MySQL的從庫可以做業務的線性擴充、實作讀寫分離、資料備份等功能。但是當主庫壓力比較大的時候,就會産生一個讓人頭疼的問題,那就是主從複制延遲。主從複制延遲會導緻從庫資料落後于主庫,産生諸多問題。
1.2降低複制延遲的方法
·增大從庫innodb_buffer_pool_size的值,使從庫可以緩存更多資料,降低IO壓力。
·增大innodb_log_file_size、innodb_log_files_in_group的值,降低刷盤IO,提升寫入性能。
·将innodb_flush_method設定為O_DIRECT,提升寫入性能。
·如果沒有特殊需求,關閉從庫binlog。
·将參數master_info_repository和relay_log_info_repository設定為TABLE,降低IO壓力。
上文這些方法雖然可以降低複制延遲,但是這始終無法真正解決複制延遲問題。
二、MySQL5.6的多線程複制
除了上文的優化方法,其實MySQL還有一種自帶的手段,就是開啟MySQL的多線程複制。
2.1MySQL5.6多線程複制的實作
對于某一個庫來說,它會被綁定到第一個執行它的線程上,這裡的綁定不是說以後該資料庫的事件都會由該線程執行,還受制于另一個條件:coordinator線程配置設定事件時以事務為機關,一個事務會配置設定給該事務中第一個庫所綁定worker線程,不會被拆分。如果遇到一個新的庫,不能按照上面的規則決定執行的資料庫的(即沒有綁定線程,而且是該事務中第一個庫)則會尋找綁定庫最少的worker線程來執行它。
涉及多庫操作的語句,在配置設定這個語句時,coordinator線程會等待這些庫的綁定線程都執行完畢,然後再配置設定這個語句。而如何涉及到的庫太多(大于254)或者是一個ddl語句,則會觸發一次同步操作,即等待所有線程執行完畢,然後将它配置設定給0号worker線程。
2.2MySQL5.6的多線程複制的缺點
MySQL5.6的多線程複制要求資料庫數量比較多,并且各個庫的資料要分布均勻。換句話說,MySQL5.6的多線程複制是基于庫級别的,如果binlog row event操作的是不同的schema的對象,在沒有DDL的情況下,就可以實作多線程複制。
但是在實際業務場景中,一庫多表很常見,多庫少表卻很少見。這個原因導緻MySQL5.6的多線程複制并不适合在實際環境應用,但是不得不承認這的确一個解決複制延遲問題很好的手段。
三、MySQL5.7的多線程複制
MySQL5.6的多線程複制在實際應用起到的作用卻很小。直到MySQL5.7版本,MySQL才“真正”支援多線程複制功能,官方稱為為enhanced multi-threaded slave(簡稱MTS)。
3.1MySQL的組送出
在介紹MySQL5.7的多線程複制前,我要先介紹一下組送出。MySQL5.7相對于MySQL5.6在GTID中增加了兩個事件(last_committed和sequence_number),根據這兩個事件就可以明白什麼是組送出:
#190528 9:50:18 server id 330601 end_log_pos 971 CRC32 0x0d6b7893 GTID last_committed=2 sequence_number=3 rbr_only=yes
#190528 9:50:23 server id 330601 end_log_pos 1288 CRC32 0x02f70237 GTID last_committed=3 sequence_number=4 rbr_only=yes
#190528 9:50:24 server id 330601 end_log_pos 1605 CRC32 0xd613b4bf GTID last_committed=4 sequence_number=5 rbr_only=yes
可以看到以往binlog中下一個事務的last_committed永遠都和上一個事務的sequence_number是相等的。
#190629 21:21:07 server id 330601 end_log_pos 1420595 CRC32 0xee9fca87 GTID last_committed=3460 sequence_number=3506 rbr_only=yes
#190629 21:21:07 server id 330601 end_log_pos 1420998 CRC32 0x64a67854 GTID last_committed=3460 sequence_number=3507 rbr_only=yes
#190629 21:21:07 server id 330601 end_log_pos 1421401 CRC32 0x89930386 GTID last_committed=3460 sequence_number=3508 rbr_only=yes
在MySQL5.7開啟組送出的binlog中各事務的last_committed是相同的,這意味着多個事務是作為一個組送出的,這些事務在perpare階段擷取相同的last_committed而且互相不影響,最終是會作為一個組進行送出。這就是所謂的組送出。
MySQL5.7的組送出通過參數group_commit設定。
mysql> show variables like '%group_commit%';
+-----------------------------------------+-------+
| Variable_name | Value |
+-----------------------------------------+-------+
| binlog_group_commit_sync_delay | 0 |
| binlog_group_commit_sync_no_delay_count | 0 |
+-----------------------------------------+-------+
2 rows in set (0.00 sec)
binlog_group_commit_sync_delay控制着日志在刷盤前日志送出要等待的時間,0代表送出後立即刷盤,當設定為N(大于0)的時候,就允許多個事務的日志同時間一起送出刷盤,也就是我們說的組送出。組送出是多線程複制的基礎。
binlog_group_commit_sync_no_delay_count ,這個參數優先于binlog_group_commit_sync_delay,在等待時間内,如果事務數達到binlog_group_commit_sync_no_delay_count的值,就會觸動一次組送出。
3.2MySQL5.7多線程複制的實作
MySQL期望最大化的還原主庫的并行度,實作方式是在binlog event中增加必要的資訊(last_committed和sequence_number),以便slave節點根據這些資訊實作多線程複制。
MySQL5.7使用參數slave_parallel_type來相容5.6的多線程複制;通過參數slave_parallel_workers指定多線程複制的線程數。
mysql> show variables like '%slave_parallel_type%';
+---------------------+---------------+
| Variable_name | Value |
+---------------------+---------------+
| slave_parallel_type | LOGICAL_CLOCK |
+---------------------+---------------+
1 row in set (0.01 sec)
mysql> show variables like '%slave_parallel_workers%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| slave_parallel_workers | 8 |
+------------------------+-------+
1 row in set (0.01 sec)
MySQL5.7的多線程複制建立在group commit的基礎上,所有在主庫上能夠完成prepared的語句表示沒有資料沖突,就可以在slave節點多線程複制。
四、MySQL事務送出方式及多線程複制分發
4.1MySQL5.7中事務送出方式
binlog的組送出是通過 Stage_manager 管理,其中比較核心内容如下:
class Stage_manager {
public:
enum StageID { // binlog的組送出包括了三個階段
FLUSH_STAGE,
SYNC_STAGE,
COMMIT_STAGE,
STAGE_COUNTER
};
private:
Mutex_queue m_queue[STAGE_COUNTER];
};
binlog的組送出的三個階段主要執行的工作内容為:
InnoDB, Prepare
SQL已經成功執行并生成了相應的redo和undo記憶體日志;
Binlog, Flush Stage
所有已經注冊線程都将寫入binlog緩存;
Binlog, Sync Stage
binlog緩存将sync到磁盤,sync_binlog=1時該隊列中所有事務的binlog将永久寫入磁盤;
InnoDB, Commit stage
leader根據順序調用存儲引擎送出事務;
每個階段都有各自的隊列,進而使每個會話的事務進行排隊,提高并發性能。如果當一個線程注冊到一個空隊列時,該線程就做為該隊列的leader,後注冊到該隊列的線程均為follower,後續的操作都由leader控制隊列中follower行為。
leader同時會帶領目前隊列的所有follower到下一個階段 去執行,當遇到下一個階段為非空隊列時,leader會變成follower注冊到此隊列中。
4.2多線程複制分發原理
當slave_parallel_workers參數設定成n時,會有n個worker線程,由它來執行event,原來的sql線程變成coordinator線程,由它來讀取relay log,并按照一定規則将讀到的event配置設定給worker線程執行。從庫是以事務為機關進行APPLY的,每一個事務有一個GTID事件,都有一個last_committed和sequence_number,多線程複制分發原理如下:
1、從庫SQL線程讀取一個新事務,拿出last_committed和sequence_number的值。
2、判斷目前last_committed是否大于目前已經執行的sequence_number的最小值。如果大于,則說明上一組事務還沒有完成,需要等待last_committed等于sequence_number後繼續;否則說明目前事務和正在執行在同一組,直接繼續。
3、SQL線程尋找worker線程,将目前事務交給worker,worker線程去APPLY這個事務。
五、總結
多線程複制從MySQL5.6到MySQL5.7的發展,真正地實作多線程複制,解決了複制延遲問題!