一、復制延遲的現象問題
說到復制延遲。我曾經(現在也是)眼睜睜的看着每天好幾封告警郵件,有一半是來自復制延遲,卻又奈何不了。估計很多 MySQL DBA也是對他恨到牙癢癢。
二、MySQL官方給出的解決方案
2.1 5.6 --> 基於庫級別的並行復制
MySQL中可能會有多個庫,不同的庫之間可能沒有什么關系,所以在slave那邊為每一個庫分配了一個線程。以此提高復制的效率。也有可能會出現跨庫的情況,當出現這種情況,也就這能等待這個事務完成
2.2 5.7 --> 基於組提交的並行復制
組提交的思想是:所有處於 prepare 階段的事務同屬於一個組,一個組內的事務可以並行提交。
基於此思想,同組事務到了 slave 端就可以並行處理。
那么在 slave 端是怎么區分是同組事務呢?
MySQL在binlog中加入了 last_committed,sequence_number
#180915 2:59:19 server id 883306 end_log_pos 259 CRC32 0xad68c74f GTID last_committed=0 sequence_number=1 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 582 CRC32 0x347b8079 GTID last_committed=1 sequence_number=2 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 905 CRC32 0x9728debf GTID last_committed=1 sequence_number=3 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 1228 CRC32 0x5a8c2d37 GTID last_committed=1 sequence_number=4 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 1551 CRC32 0x79d0f774 GTID last_committed=1 sequence_number=5 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 1874 CRC32 0xc75b96fa GTID last_committed=1 sequence_number=6 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 2197 CRC32 0x03bbc228 GTID last_committed=1 sequence_number=7 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 2520 CRC32 0x876377ab GTID last_committed=1 sequence_number=8 rbr_only=yes
last_committed 相同的屬於同一個組,例如:
last_committed = 0 中只有 1 個事務
last_committed = 1 中有 2-8 7個事務
假設 slave 端的 parallel 是 10,那么就可以同時執行7個事務。雖然看着挺美好,但還是有個問題。介紹這個問題的時候,首先介紹兩個參數:
binlog_group_commit_sync_delay:等待多少時間后才進行組提交,單位 (ms),最大 1000000ms == 1s
binlog_group_commit_sync_no_delay_count:等待一組里面有多少事物我才提交
以上兩個參數,默認都是0,意思是 MySQL 不等待就進行提交。所以當系統不繁忙是,last_committed 通常只有 1 個 sequence_number。也就是一組只有一個事務。而當系統比較繁忙時,last_commttied 中有可能有多個 sequence_number。這個由 MySQL 自己決定。但是MySQL本身決定是有問題的,假如 1 秒 中有 500 個事務(這個在我們的生產系統中最高達到1000,業務高峰300-500都是常見的)。但是一組中的事物並非都是 10 個以上的,基本上都是 1、2、3 …… 參差不齊的。所以我的從庫本來是有 10 個並行線程的。但是最能同時處理 1、2、3個事務,你說這讓人氣不氣。那么不是有 binlog_group_commit_sync_no_delay_count 這個參數嗎?可以控制一組有多少事務才提交,我設置為 10,那么到從庫就可以 10 個事務並行處理了啊。是這樣沒錯,但是設置這個 binlog_group_commit_sync_no_delay_count 參數之前,需要打開 binlog_group_commit_sync_delay,否則不生效。
那么既然需要打開,那就打開唄!設置多少好呢?假設設置到最大 1s,1s 最大 500個事務,開50個並行,每個並行每秒處理 10 個事務,每個事物 0.1 秒,0.1 秒從庫處理的過來。從庫沒有延遲很開心有沒有,但是這種設置有沒有問題,有。看一下 binlog_group_commit_sync_delay 的解釋,等待多少時間后才進行組提交。這個時間,不管你 1s 內有多少個事務,統統等待 1s。也就是我一個 insert 本來 0.01 可以完成的,我非要等待 1 秒后才提交。並發高的時候沒有影響,並發低的時候問題就來了。非要阻塞 1s 才提交。還會有什么其他影響嗎?有,沒有提交是不是就持有鎖,那么鎖沒釋放,其他會話是不是就需要等待。所以,我曾經沒有好好理解 等待多少時間后才進行組提交 這句話,導致了生產事故。具體看我寫的另一篇博客:https://www.cnblogs.com/ziroro/p/9600359.html
2.3 5.7.22以上 --> 基於寫集合的並行復制
一不小心說了大多血淚史,回歸正題。writeset的思想是:不同事物修改了不同行的數據,那么可以視為同一組。MySQL 會對這個提交的事務中的一行記錄做一個 HASH值,這些 HASH 值稱為 writeset。writeset會存入一張 HASH 表。其他事務提交時會檢查這張 HASH 表中是否有相同的記錄,如果不相同,則視為同組,如果有相同,則視為不同組。怎么判斷是否同組,依然采用了last_committed,sequence_number。具體看實驗:
2.3.1 binlog_transaction_dependency_tracking 設置為 writeset
set global binlog_transaction_dependency_tracking = WRITESET
2.3.2 插入不相同的數據
insert into t1 select 1,'a'; insert into t1 select 2,'b';
2.3.3 解析binlog
mysqlbinlog --base64-output=decode-rows -vv /data/mysql/mysql_3306/logs/bin.000004 > 4.sql
內容如下
### INSERT INTO `vcyber`.`t1` ### SET ### @1=1 /* INT meta=0 nullable=0 is_null=0 */ ### @2='a' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:19:32 server id 883306 end_log_pos 572 CRC32 0x88c31fe8 GTID last_committed=1 sequence_number=2 rbr_only=yes ………… ### INSERT INTO `vcyber`.`t1` ### SET ### @1=2 /* INT meta=0 nullable=0 is_null=0 */ ### @2='b' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:19:35 server id 883306 end_log_pos 885 CRC32 0x6901dc68 GTID last_committed=1 sequence_number=3 rbr_only=yes
可以看到修改了不相同的數據,last_committed 都是相同的。為了比較不同,把 binlog_transaction_dependency_tracking 修改回 COMMIT_ORDER
2.3.4 binlog_transaction_dependency_tracking 設置為 COMMIT_ORDER
set global binlog_transaction_dependency_tracking = COMMIT_ORDER
2.3.5 插入不相同的數據
insert into t1 select 11,'aa'; insert into t1 select 12,'bb';
2.3.6 解析binlog
### INSERT INTO `vcyber`.`t1` ### SET ### @1=11 /* INT meta=0 nullable=0 is_null=0 */ ### @2='aa' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:30:30 server id 883306 end_log_pos 575 CRC32 0x24a2ace1 GTID last_committed=1 sequence_number=2 rbr_only=yes ………… ### INSERT INTO `vcyber`.`t1` ### SET ### @1=15 /* INT meta=0 nullable=0 is_null=0 */ ### @2='ee' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:30:34 server id 883306 end_log_pos 891 CRC32 0xe3bb6418 GTID last_committed=2 sequence_number=3 rbr_only=yes
兩個事物的 last_committed 不相同,到從庫是沒有辦法並行復制的。
那么 writeset 最多可以並行執行多少個事務呢?假設插入 30000 條不同記錄,統計下 last_committed 有多少個,就可以證明可以並行執行多少個事務,為了少啰嗦一點,我就直接貼出我的結果
12500 last_committed=1 12501 last_committed=12501 4997 last_committed=25002
看來好像最多可以並發執行 12500 事務。12500 這個數字接近 binlog_transaction_dependency_history_size 這個參數的一半
binlog_transaction_dependency_history_size:哈希表可以存儲的最大大小
為了證明猜想,將 binlog_transaction_dependency_history_size 設置為 50000 又會怎么樣
25000 last_committed=1 25001 last_committed=25001 9997 last_committed=50002
看起來好像確實接近於 binlog_transaction_dependency_history_size 的一半。