前面介紹的MySQL 的主從復(fù)制流程如下所示:
主備延遲的主要原因在于,master A 上產(chǎn)生 binlog 的速度大于slave B 處理 binlog 的速度。數(shù)據(jù)的積壓就在于 sql_thread 處理的速度。在 MySQL 5.6 版本之前剑梳,只支持單線程復(fù)制。單線程的 binlog 復(fù)制,在高并發(fā)的場(chǎng)景下會(huì)出現(xiàn)嚴(yán)重的主從不一致猎塞。要解決這個(gè)問(wèn)題,就需要將上面的 sql_thread 拆解成為多個(gè)線程處理杠纵。
上圖中的 coordinate 就是本文開始的 sql_thread荠耽,具體執(zhí)行 binlog 復(fù)制的是 Worker。生產(chǎn)上比藻,Worker 的個(gè)數(shù)設(shè)置為 8-16 個(gè)比較合適(32核CPU)铝量,因?yàn)閺膸?kù)還需要可能還需要處理讀的請(qǐng)求。
對(duì)于 coordinate 的分發(fā)策略银亲,并不能是隨機(jī)的慢叨,因?yàn)檫@樣對(duì)于SQL 執(zhí)行不同的順序,可能會(huì)產(chǎn)生不同的結(jié)果务蝠。此時(shí)對(duì)于 coordinate 的分發(fā)策略要求如下:
不能造成更新覆蓋拍谐,同一行的更新必須被分發(fā)到同一個(gè)Worker 中;
同一個(gè)事務(wù)不能被拆開馏段,必須被分配到同一個(gè)Worker 中轩拨。
按表分發(fā)策略
按表分發(fā)的基本思路是,如果兩個(gè)事務(wù)更新不同的表院喜,就可以并行處理气嫁。此時(shí)因?yàn)樵诓煌?Worker 處理時(shí),也不會(huì)更新到同一行的數(shù)據(jù)够坐。如果有跨表的事務(wù)寸宵,則需要 2 張表都需要考慮了。
從上圖可以看出元咙,每個(gè) Worker 都對(duì)應(yīng)一個(gè) hash 表梯影,用于保存當(dāng)前 Worker 執(zhí)行的 binlog 涉及的表。hash 表的 key 是 “庫(kù)名+表名”庶香,value 表示 Worker 中有多少個(gè)事務(wù)操作這個(gè)表甲棍。當(dāng)事務(wù)執(zhí)行完成后,其所涉及的表的計(jì)數(shù)會(huì)從hash 表中移除赶掖。上面 Worker_1 中的hash 表中 db1.t1:4 感猛,表示:Worker_1 中修改 db1.t1 表的事務(wù)數(shù)有 4 個(gè)七扰。
假設(shè) coordinate 讀取一個(gè)事務(wù) T (涉及 t1 和 t3的改動(dòng)),此時(shí)分配規(guī)則如下:
Worker_1 中有事務(wù)在處理 t1 的改動(dòng)陪白,此時(shí)和 Worker_1 是沖突的颈走,對(duì)于Worker_2 有在處理 t3 的改動(dòng),此時(shí)和Worker_2 也是沖突的咱士;
當(dāng)事務(wù) T 和多于 1 個(gè)Worker 沖突時(shí)立由,coordinate 就進(jìn)入等待狀態(tài);
此時(shí)如果 Worker_2 中執(zhí)行完事務(wù)后序厉,對(duì)應(yīng) t3 的計(jì)數(shù)就會(huì)減 1锐膜,此時(shí)事務(wù) T 就只和 Worker_1 沖突了,此時(shí)就會(huì)將其加入到 Worker_1 的隊(duì)列中弛房;
coordinate 讀取新事務(wù) T2 繼續(xù)執(zhí)行 步驟 1 道盏。
總結(jié)以上,coordinate 在分發(fā)事務(wù)的時(shí)候文捶,會(huì)考慮以下沖突情況:
當(dāng)沒(méi)有 Worker 沖突的時(shí)候荷逞,會(huì)將其加入到空間的Worker 中去;
當(dāng)有只有 1 個(gè)Worker 沖突的時(shí)候拄轻,會(huì)將其加入到?jīng)_突的Worker 中去;
當(dāng)有多于 1 個(gè)Worker 沖突的時(shí)候伟葫,coordinate 會(huì)進(jìn)入等待狀態(tài)恨搓,直到?jīng)_突數(shù) <= 1。
按行分發(fā)策略
按行分發(fā)的策略和按表分發(fā)的策略類似筏养,但是在考慮Worker 沖突的時(shí)候?qū)?yīng)的hash key 就是“庫(kù)名+表名+唯一鍵的值”斧抱,原理類似,這里就不做過(guò)多的介紹了渐溶。這里需要注意的點(diǎn)是辉浦,按行進(jìn)行沖突檢測(cè),其消耗的計(jì)算量還是比較大的咧欣。
MySQL5.6 的并行復(fù)制
MySQL 從 5.6 版本開始支持按庫(kù)級(jí)別的并行復(fù)制娶桦。對(duì)于一個(gè)應(yīng)用來(lái)說(shuō)昼激,如果我們按照業(yè)務(wù)分庫(kù),從應(yīng)用的層面上面講是提高了 binlog 的復(fù)制能力的弛槐。按庫(kù)級(jí)別的并行相比按表和按行級(jí)別的,有以下 2 個(gè)優(yōu)勢(shì):
構(gòu)造 Worker 的 hash 表很快依啰,因?yàn)閹?kù)的個(gè)數(shù)不會(huì)太多乎串,而且計(jì)算量也會(huì)減少;
不需要要求 binlog 的格式速警。
MariaDB 的并行復(fù)制
在之前介紹的 redo log 組提交時(shí)叹誉,有以下特點(diǎn):
在一個(gè)組里提交的事務(wù)鸯两,一定不會(huì)修改同一行;
主庫(kù)上面可以并行的事務(wù)长豁,在從庫(kù)上面也是可以并行的钧唐。
MariaDB 并行復(fù)制的實(shí)現(xiàn)上,操作流程如下:
在一個(gè)組里面提交的事務(wù)有一個(gè)相同的 commit_id蕉斜,下一個(gè)組就是 commit_id + 1逾柿;
commit_id 直接寫到 binlog 里面;
傳到備庫(kù)的時(shí)候宅此,相同 commit_id 的事務(wù)會(huì)被分發(fā)到不同的 Worker 中執(zhí)行机错;
這一組執(zhí)行完成后,再去取下一組重復(fù)以上步驟父腕。
從上面流程可以看出弱匪,下一個(gè)組提交的執(zhí)行依賴上一個(gè)組提交的執(zhí)行完成。此時(shí)如果上一個(gè)組提交中有大事務(wù)璧亮,就會(huì)影響下一個(gè)組提交的執(zhí)行萧诫,容易造成阻塞。
MySQL5.7 的并行復(fù)制
MariaDB 在實(shí)現(xiàn)了并行復(fù)制能力之后枝嘶,MySQL 也提供了類似的功能帘饶。由 slave-parallel-type 參數(shù)來(lái)控制并行復(fù)制的策略:
配置為 DATABASE,表示使用 MySQL 5.6 開始提供的按庫(kù)并行復(fù)制的策略群扶;
配置為 LOGICAL_CLICK及刻,表示就是使用類似于 MariaDB 并行復(fù)制策略。
對(duì)于 LOGICAL_CLICK 這種策略竞阐,MySQL 5.7 對(duì)其做了優(yōu)化缴饭。說(shuō)優(yōu)化之前,我們先看一下之前提到的“事務(wù)兩階段提交的細(xì)化流程”骆莹。
上面提到的 MariaDB 并行復(fù)制的核心是:一個(gè)組內(nèi)颗搂,已經(jīng)提交的事務(wù)是可以并行的。但是從上面流程可以看出幕垦,只要 redo log prepare (第一步)完成之后丢氢,事務(wù)之間就已經(jīng)完成沖突檢測(cè)了。因此 MySQL 5.7 的優(yōu)化思想如下:
同時(shí)處于 prepare 階段的事務(wù)是可以并行執(zhí)行的先改;
處于 prepare 階段的事務(wù)與處于 commit 狀態(tài)的事務(wù)之間卖丸,也是可以并行執(zhí)行的。
前面在 binlog 組提交的時(shí)候盏道,介紹了下面 2 個(gè)參數(shù):
binlog_group_commit_sync_delay 參數(shù):表示延遲多少個(gè)微妙之后稍浆,再執(zhí)行 fsync;
binlog_group_commit_sync_no_delay_count 參數(shù):表示累計(jì)多少次之后,再執(zhí)行 fsync衅枫。
上面 2 個(gè)參數(shù)是提高組提交里面的事務(wù)批量嫁艇,簡(jiǎn)單的理解可以是:減慢主庫(kù)的 binlog 寫入,讓備庫(kù)能趕得上弦撩。
MySQL5.7.22 的并行復(fù)制
在 2018年4月發(fā)布的 5.7.22 版本里面步咪,新增了基于 writeset 的并行復(fù)制。新增了
binlog-transaction-dependency-tracking益楼,用來(lái)控制是否啟用這個(gè)新策略猾漫,這個(gè)參數(shù)有以下 3 個(gè)可選值:
COMMIT_ORDER:就是前面提到的處于 prepare 和 commit 狀態(tài)的 binlog 都可以被分發(fā)到 Worker 上面處理;
WRITESET:對(duì)于事務(wù)更新的每一行感凤,都計(jì)算出一個(gè) hash 值悯周,組成集合 writeset。如果兩個(gè)事務(wù)的 writeset 沒(méi)有交集陪竿,說(shuō)明事務(wù)沒(méi)有操作相同的行禽翼,事務(wù)之間是可以并行的;
WRITESET_SESSION:是在 WRITESET 的基礎(chǔ)上新增了一個(gè)約束族跛,就是在主庫(kù)上面同一個(gè)線程執(zhí)行的 2 個(gè)事務(wù)的執(zhí)行順序闰挡,在從庫(kù)上面也需要保證順序性。
可以看出礁哄,MySQL 5.7.22 提出的并行復(fù)制策略和之前說(shuō)的按表长酗、按行的并行復(fù)制原理類似。另外其還有一些優(yōu)化點(diǎn):
writeset 是在主庫(kù)上面生成后直接寫到 binlog 里面的桐绒,這樣在備庫(kù)執(zhí)行時(shí)夺脾,就需要解析 binlog 的內(nèi)容,節(jié)省了很多計(jì)算量掏膏;
不需要把整個(gè)事務(wù)的 binlog 都掃描一遍后劳翰,才決定分發(fā)到哪個(gè) Worker敦锌,更節(jié)省內(nèi)存馒疹;
由于備庫(kù)的分發(fā)策略不依賴于 binlog 的內(nèi)容,因此對(duì) binlog 的格式?jīng)]有要求乙墙。
當(dāng)然上面所說(shuō)的并行復(fù)制的前提都是颖变,沒(méi)有外鍵約束,所有表都有主鍵的場(chǎng)景听想。如果不滿足腥刹,則會(huì)退化成單線程的模式。