天天看點

各版本MySQL并行複制的實作及優缺點

mysql并行複制已經是老生常談,筆者從2010年開始就着手處理線上這個問題,剛開始兩三年也樂此不疲分享,現在再提這個話題本來是難免“炒冷飯”嫌疑。

 最近觸發再談這個話題,是因為有些同學覺得“5.7的并行複制終于徹底解決了複制并發性問題”, 感覺還是有必要分析一下。大家都說沒有銀彈,但是又期待銀彈。。

既然要說5.7的并行複制,幹脆順手把各個版本的并行複制都說明一下,也好有個對比。便是本次分享的初衷。

【背景】

一句話說完,因為這幾年太多這樣文章了, 就是mysql一直以來的備庫複制都是單線程apply。

【解決基本思路】

改成多線程複制。

備庫有兩個線程與複制相關:io_thread 負責從主庫拿binlog并寫到relaylog, sql_thread 負責讀relaylog并執行。

各版本MySQL并行複制的實作及優缺點

多線程的思路就是把sql_thread 變成分發線程,然後由一組worker_thread來負責執行。

幾乎所有的并行複制都是這個思路,有不同的,便是sql_thread 的分發政策。

各版本MySQL并行複制的實作及優缺點

而這些政策裡面又分成兩類:利用傳統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出來以後,在中國并沒有什麼反響。

但是這個政策也要說也是有優點的:

1、對于可以按表分發的場景,可以通過将表遷到不同的庫,來應用此政策,有可操作性

2、速度更快,因為hash key就一個庫名

3、不要求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代碼的。

實際上按組直接分段這個政策略顯粗暴。實際上事務送出并不是一個點,而是一個階段。至少我們可以分成:準備送出、送出中、送出完成。

這三個階段都是在事務已經完成了主要操作邏輯,進入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 非常久的情況,仍然存在拖後腿的問題。

 【小結】

按粒度區分的三個政策,粒度從粗到細是按庫、按表、按行。

這三個的對比中,并行度越來越大,額外損耗也是。無關大事務不會影響并發度。

按照commit_id 的兩個政策,适用範圍更廣,額外消耗也低。

5.7的改進政策并發性更優。但出現大事務會拖後腿。

另外,很重要的一點,5.7的政策目的是“模拟主庫并發”,是以對于主庫單線程更新是無加速作用的。而基于沖突的前三個政策,若滿足并發條件,會出現備庫比主庫執行速度快的情況。這種需求在搭備庫或者延遲複制的場景中可能觸發。

實際上政策的選擇取決于應用場景,這是架構師的工作之一。

ps:具體5.7的實作原理可參考我們團隊的@印風 同學的部落格  http://mysqllover.com/?p=1370 (最後一個例子的case也從此摘錄)