[轉載自阿里丁奇]各版本MySQL並行複製的實現及優缺點

MySQL並行複製已是老生常談,筆者從2010年開始就着手處理線上這個問題,剛開始兩三年也樂此不疲分享,如今再提這個話題原本是不免「炒冷飯」嫌疑。
 
 最近觸發再談這個話題,是由於有些同窗以爲「5.7的並行複製終於完全解決了複製併發性問題」, 感受仍是有必要分析一下。你們都說沒有銀彈,可是又期待銀彈。。
 
既然要說5.7的並行複製,乾脆順手把各個版本的並行複製都說明一下,也好有個對比。即是本次分享的初衷。
 
【背景】
一句話說完,由於這幾年太多這樣文章了, 就是MySQL一直以來的備庫複製都是單線程apply。
 
【解決基本思路】
改爲多線程複製。
 
備庫有兩個線程與複製相關:io_thread 負責從主庫拿binlog並寫到relaylog, sql_thread 負責讀relaylog並執行。
 
 
多線程的思路就是把sql_thread 變成分發線程,而後由一組worker_thread來負責執行。
幾乎全部的並行複製都是這個思路,有不一樣的,即是sql_thread 的分發策略。
 
 
而這些策略裏面又分紅兩類:利用傳統binlog格式、修改binlog。
 
使用傳統的binlog格式的幾類,因爲binlog裏面的信息就那些,所以只能按照粒度來分,也就是:按庫、按表、按行
 
另外有兩個策略是修改了binlog格式的,在binlog裏面增長了別的信息,用於體現提交分組。
 
下面咱們分別介紹幾個並行複製的實現。
 
【5.5】
 
MySQL官方5.5是不支持並行複製的。可是在阿里的業務須要並行複製的年份,尚未官方版本支持,只好本身實現。並且從兼容性角度說,不修改binlog格式,因此採用的是利用傳統binlog格式的改造。
 
阿里的版本支持兩種分發策略:按表和按行。
 
前情說明,因爲MySQLbinlog日誌還有用於別的系統的要求,所以阿里的binlog格式都是row----這也給並行複製的實現減小了難度。
 
按表分發策略:row格式的binlog,每一個DML前面都是有Table_map event的。所以很容易拿到庫名/表名。一個簡單的思路是,不一樣表的更新之間是不須要嚴格按照順序的。
所以按照表名hash,hash key是 庫名+表名,相同的表的更新放到同一個worker上。這樣就保證同一個表的更新順序,跟主庫上是同樣的。
 
應用場景:對於多表更新的場景效果特別好。缺點是反之的,如果熱點表更新,則本策略無效。並且因爲hash表的維護,性能反而降低。
 
按行分發策略:row格式的binlog中,也不難拿到主鍵ID.  有同窗說若是沒有主鍵怎麼辦,答案是"起開,如今誰還沒主鍵:)"。好吧,正經答案是沒有主鍵就不支持這個策略。
一樣的,咱們認爲不一樣行的更新,能夠無序併發的。只要保證同一行的數據更新,在備庫上的順序與主庫上的相同便可。
所以按照主鍵id hash,因此這個hash key更長,必須是 庫名+表名+主鍵id。相同行的更新放到同一個worker上。
 
須要注意的是,上面的描述看上去都是對單個event的操做,實際上 並不能!由於備庫可能接受讀,所以事務的原子性是要保證的,也就是說,對於涉及多個更新操做的事務,每次用於決策的不是一個hash key,而是一組。
應用場景:熱點表更新。缺點,hash key計算衝突的代價大。尤爲是大事務,計算hash key的cpu消耗大,並且耗內存。這須要業務DBA作判斷得失。
 
【5.6】
官方的5.6支持的是按庫分發。有了上面的背景,你們就知道,這個feature出來之後,在中國並無什麼反響。
 
可是這個策略也要說也是有優勢的:
 
一、對於能夠按表分發的場景,能夠經過將表遷到不一樣的庫,來應用此策略,有可操做性
 
二、速度更快,由於hash key就一個庫名
 
三、不要求binlog格式,你們知道不管是row仍是statement格式,都是可以輕鬆獲取庫名的。
因此並非徹底沒有用的。仍是習慣問題。
 
 
【MariaDB】
 
MariaDB的並行複製策略看上去有好幾個選項,然而生產上可用的也就是默認值的 CONSERVATIVE。
 
因爲maraiaDB支持多主複製,一個domain_id字段是用來標示事務來源的。若是來自於不一樣的主,天然能夠並行(這個其實也是通用概念,還得業務DBA本身判斷)。
 
對於同一個主庫來的binlog,用commit_id 來決定分組。
 
想法是這樣的:在主庫上同時提交的事務設置成相同的commit_id。在備庫上apply時,相同的commit_id能夠並行執行,由於這意味着這些事務之間是沒有行衝突的(不然不可能同時提交)。
 
這個思路跟最初從單線程改爲多線程同樣,我的認爲是劃時代的。
 
可是也並無解決了全部的問題。這個策略最怕的是,拖後腿事務。
 
設想一下這個場景,假設某個DB裏面正在做大量小更新事務(好比每一個事務更新一行),這樣在備庫就並行得很歡樂。
 
而後忽然,在同一個實例,另一個庫下,或者同一個庫的另一個跟目前的更新無關的表,忽然有一個delte操做刪除了10w行。
 
delete事務在提交的時候,跟當時一塊兒提交的事務都算同一個commit_id。假設爲N.
 
以後的小事務更新提交組commit_id爲N+1。
 
到備庫apply時,就會發現N這個組裏面,其餘小事務都執行完了,線程進入空閒狀態,可是不能繼續執行N+1這個commit_id的事務,由於N裏面還有一個大事務沒有執行完成,這個咱們認爲是拖後腿的。
 
而基於傳統binlog格式的上面三個策略,反而沒有這個問題。只要是策略上可以判斷不衝突,大事務本身有個線程跑,其餘事務繼續並行。
 
【5.7】
 
MySQL官方5.7版本也是及時跟進,先引入了上述MariaDB的策略。固然從版權安全上,oracle是不會容許直接port代碼的。
 
而後官方5.7的新版本在此之上繼續優化。 
 
實際上按組直接分段這個策略略顯粗暴。實際上事務提交併非一個點,而是一個階段。至少咱們能夠分紅:準備提交、提交中、提交完成。
 
這三個階段都是在事務已經完成了主要操做邏輯,進入commit狀態了。
 
同時進入「提交中」狀態的算同一個commit_id. 可是實際上,在任意時刻,處於」準備提交」的事務,與「提交中」的事務,也是能夠並行的。可是明顯他們會被分紅兩個不一樣的commit_id。
 
這意味着這個策略還有提高併發度的空間。
 
咱們來看一下兩種策略的對比差異。
 
假設主庫有以下面示意圖的事務序列。每一個事務提交過程當作兩個階段,prepare ... commit. 分別給不一樣的編號。其中commit對應的數字是天然數遞增,sequence_no。而prepare是對應的數字是X+1,這個X表示的是當前已經提交完成的sequence_no。
trx1 1…..2
trx2 1………….3
trx3 1…………………….4
trx4        2………………………….5
trx5               3………………………………..6
trx6               3………………………………………………7
trx7                                                       6……………………..8
 
分析:
在MariaDB的策略裏面,併發執行序列以下:
trx1, trx2, trx3 ----group 1
trx4 -----group 2
trx 5, trx6 ----group 3
trx 7 ----group 4
每一個group 執行完成後,下一個group 才能夠開始。
徹底執行完成的時間是每一個group的最大事務時間之和,即 trx3 + trx4+trx6+trx7。
所以,若是某個group裏面有一個很大的事務,則整個序列的執行時間就會被拖久。
 
再來看5.7的改進策略:
雖然也是group1先啓動,可是在trx1完成後, trx4就能夠開始執行;
一樣的,trx7能夠在trx4執行完成後就開始執行,與trx5和trx6併發。
所以能夠說上面這個例子中,備庫apply過程徹底達到了主庫執行的併發度。
可是對於大事務,好比trx2 commit 很是久的狀況,仍然存在拖後腿的問題。
 
 【小結】
咱們看到,就並行複製,有5種策略。
按粒度區分的三個策略,粒度從粗到細是按庫、按表、按行。
這三個的對比中,並行度愈來愈大,額外損耗也是。無關大事務不會影響併發度。
 
按照commit_id 的兩個策略,適用範圍更廣,額外消耗也低。
5.7的改進策略併發性更優。但出現大事務會拖後腿。
 
另外,很重要的一點,5.7的策略目的是「模擬主庫併發」,因此對於主庫單線程更新是無加速做用的。而基於衝突的前三個策略,若知足併發條件,會出現備庫比主庫執行速度快的狀況。這種需求在搭備庫或者延遲複製的場景中可能觸發。
 
實際上仍是老話,沒有萬用的策略。

策略的選擇取決於應用場景,這是架構師的工做之一。
 
PS:具體5.7的實現原理可參考咱們團隊的@印風 同窗的博客  http://mysqllover.com/?p=1370 (最後一個例子的case也今後摘錄)
相關文章
相關標籤/搜索