天天看點

MySQL 複制原理詳解

作者:譚其文

mysql作為一個開源的資料庫,有着廣泛的應用。本文主要講述了mysql複制的原理,以及異步複制,同步複制和并行複制。

Mysql有兩種複制原理:基于行的複制和基于語句的複制。最早出現的是基于語句的複制,而基于行的複制方式在5.1版本中才被引入。這兩種方式都是通過在主庫上記錄二進制日志(binlog)、在備庫上重放日志的方式來實作異步的資料複制。這意味着、在同一時間點備庫上的資料可能和主庫不一緻,并且無法保證主庫備庫之間的延遲。

基于語句的複制模式下,主庫會記錄那些造成資料更改的查詢,當備庫讀取并重放這些事件時,實際上隻把主庫上執行過的SQL再執行一遍。

優點:

最明顯的好處是實作相當簡單。理論上講,簡單地記錄和執行這些語句,能夠讓備庫保持同步。另外好處是binlog日志裡的事件更加緊湊,是以相對而言,基于語句的模式不會使用太多帶寬。一條更新好幾兆資料的語句在二進制日志裡可能隻占用幾十位元組。

缺點:

有些資料更新語句,可能依賴其他因素。例如,同一條sql在主庫和備庫上執行的時間可能稍微或很不相同,是以在傳輸的binlog日志中,除了查詢語句,還包括一些中繼資料資訊,如目前的時間戳。即便如此,還存在着一些無法被正确複制的SQL,例如,使用CURRENT_USER()函數語句。存儲過程和觸發器在使用基于語句的複制模式時也可能存在問題。另外一個問題是更新必須是串行的。這需要更多的鎖。并且不是所有的存儲引擎都支援這種複制模式。

MySQL5.1開始支援基于行的複制,這種方式會将實際資料記錄在二進制日志中,跟其他資料庫的實作比較相像。

最大的好處是可以正确的複制每一行,一些語句可以呗更加有效地複制。由于無需重放更新主庫資料的查詢,使用基于行的複制模式能夠更高效地複制資料。重放一些查詢的代價會很高。例如,下面有一個查詢将資料從一個大表中彙總到小表

但是另外一方面,下面這條語句使用基于語句的複制方式代價會小很多:

由于這條語句做了全表更行,使用基于行的複制開銷會大很多,因為每一行的資料都會呗記錄到二進制日志中,這使得二進制日志時間非常龐大。另外由于語句并沒有在日志裡記錄,是以無法判斷執行了哪些sql,除了需要知道行的變化外,這在很多情況下很重要。執行基于行的過程像一個黑盒子,你無法知道伺服器正在做什麼。

由于沒有哪一種模式對所有情況都是完善的,mysql能夠在這兩種複制模式間動态切換。預設情況下使用的是基于語句的複制方式,但如果發現語句無法呗正确地複制,就切換基于行的複制模式。還可以根據需要來設定會話級别的變量binlog_format,控制二進制日志格式。

總體來說,複制有3個步驟:

1、主伺服器把資料更改記錄到二進制日志中。(這叫做二進制日志事件)

2、從伺服器把主伺服器的二進制日志拷貝到自己的中繼日志中。

3、從伺服器重放中繼日志中的事件,把更改應用到自己的資料上。

這隻是概述,每一個步驟都很複雜。下圖更清晰描述了複制的過程。

MySQL 複制原理詳解

第一步、在主伺服器上記錄二進制日志。在每個更新資料的事務完成之前,主伺服器都會将資料更改記錄到二進制日志中。即使事務在執行期間是交錯的,mysql也會串行地将事務寫入到二進制日志中。在把事件寫入二進制日志之後,主伺服器告訴存儲引擎送出事務。

第二步、從伺服器把主伺服器的二進制日志拷貝到自己的硬碟上,進入所謂的“中繼日志”中。首先,它啟動一個工作線程,叫I/O線程,這個I/O線程開啟一個普通的用戶端連接配接,然後啟動一個特殊的二進制日志轉儲程序(它沒有相應的SQL指令)。這個轉儲程序從主伺服器的二進制日志中讀取資料。它不會對事件進行輪詢。如果3跟上了主伺服器,就會進入休眠狀态并等待有新的事件發生時主伺服器發出的信号。I/O線程把資料寫入從伺服器的中繼日志中。

第三步、SQL線程讀取中繼日志,并且重放其中的事件,然後更新從伺服器的資料。由于這個線程能跟上I/O線程,中繼日志通常在作業系統的緩存中,是以中繼日志的開銷很低。SQL線程執行事件也可以被寫入從伺服器自己的二進制日志中,它對于有些場景很實用。

一般情況下,異步複制就已經足夠應付了,但由于是異步複制,備庫極有可能是落後于主庫,特别是極端情況下,我們無法保證主備資料是嚴格一緻的。比如,當使用者發起commit指令時,Master并不關心Slave的執行狀态,執行成功後,立即傳回給使用者。試想下,若一個事務送出後,Master成功傳回給使用者後crash,這個事務的binlog還沒來得及傳遞到Slave,那麼Slave相對于Master而言就少了一個事務,此時主備就不一緻了。對于要求強一緻的業務是不可以接受的,半同步複制就是為了解決資料一緻性而産生的。

為什麼叫半同步複制?先說說同步複制,所謂同步複制就是一個事務在Master和Slave都執行後,才傳回給使用者執行成功。這裡核心是說Master和Slave要麼都執行,要麼都不執行,涉及到2PC(2 Phrase Commit)。而MySQL隻實作了本地redo-log和binlog的2PC,但并沒有實作Master和Slave的2PC,是以不是嚴格意義上的同步複制。而MySQL半同步複制不要求Slave執行,而僅僅是接收到日志後,就通知Master可以傳回了。這裡關鍵點是Slave接受日志後是否執行,若執行後才通知Master則是同步複制,若僅僅是接受日志成功,則是半同步複制。對于Mysql而言,我們談到的日志都是binlog,對于其他的關系型資料庫可能是redo log或其他日志。

半同步複制如何實作?半同步複制實作的關鍵點是Master對于事務送出過程特殊處理。目前實作半同步複制主要有兩種模式,AFTER_SYNC模式和AFTER_COMMIT模式。兩種方式的主要差別在于是否在存儲引擎送出後等待Slave的ACK。先來看看AFTER_COMMIT模式,如下圖,Start和End分别表示使用者發起Commit指令和Master傳回給使用者的時間點,中間部分就是整個Commit過程Master和Slave做的事情。

MySQL 複制原理詳解

Master送出時,會首先将該事務的redo log刷入磁盤,然後将事務的binlog刷入磁盤(這裡其實還涉及到兩階段送出的問題,這裡不展開講),然後進入innodb commit流程,這個步驟主要是釋放鎖,标記事務為送出狀态(其他使用者可以看到該事務的更新),這個過程完成後,等待Slave發送的ACK消息,等到Slave的響應後,Master才成功傳回給使用者。看到圖中紅色虛線部分,這段是Master和Slave的同步邏輯,是Master-Slave一緻性的保證。

半同步複制是否能保證不丢資料?我們通過幾種場景來簡單分析下。第一種情況:假設Master第1,2步執行成功後,binlog還沒來得及傳遞給Slave,此時Master挂了,Slave作為新Master提供服務,那麼備庫比主庫要少一個事務(因為主庫的redo 和binlog已經落盤),但是不影響使用者,對于使用者而言,這個事務沒有成功傳回,那麼送出與否,使用者都可以接受,使用者一定會進行異常捕獲而重試。第二種情況,假設第3步innodb commit執行成功後,binlog還沒來得及傳遞給Slave,此時Master挂了,此時與第一種情況一樣,備庫比主庫少一個事務,但是其他使用者在3執行完後,可以看到該事務的更新,而切換到備庫後,卻發現再次讀這個更新又沒了,這個就發生了“幻讀”,如果其他事務依賴于這個更新,則會對業務邏輯産生影響。當然這僅僅是極端情況。

對于第二種情況産生的影響,AFTER_SYNC模式可以解決這一問題。與AFTER_COMMIT相比,master在AFTER_SYNC模式下,Fsync binlog後,就開始等待SLAVE同步。那麼在進行第5步innodbcommit後,即其它事務能看到該事務的更新時,Slave已經成功接收到binlog,即使發生切換,Slave擁有與Master同樣的資料,不會發生“幻讀”現象。但是對于上面描述的第一種情況,結果是一樣的。

是以,在極端情況下,半同步複制的Master-Slave會有一個事務不一緻,但是對于使用者而言,由于這個事務并沒有成功傳回給使用者,是以無論事務送出與否都是可以接受的,使用者有必要進行查詢或重試,判讀是否更新成功。或者我們想想,對于單機而言,若事務執行成功後,傳回給使用者時,網絡斷了,使用者也是面臨一樣的問題,是以,這不是半同步複制的問題。對于送出傳回成功的事務,版同步複制保證Master-Slave一定是一緻的,從這個角度來看,半同步複制不會丢資料,可以保證Master-Slave的強一緻性。下圖是AFTER_SYNC模式,事務送出過程。

MySQL 複制原理詳解

半同步複制解決了Master-Slave的強一緻問題,那麼性能問題呢?從圖1中可以看到參與複制的主要有兩個線程:IO線程和SQL線程,分别用于拉取和回放binlog。對于Slave而言,所有拉取和解析binlog的動作都是串行的,相對于Master并發處理使用者請求,在高負載下, 若Master産生binlog的速度超過Slave消費binlog的速度,導緻Slave出現延遲。如下圖,可以看到,Users和Master之間的管道遠遠大于Master和Slave之間的管道。

MySQL 複制原理詳解

那麼如何并行化,并行IO線程,還是并行SQL線程?其實兩方面都可以并行,但是并行SQL線程的收益更大,因為SQL線程做的事情更多(解析,執行)。并行IO線程,可以将從Master拉取和寫Relay log分為兩個線程;并行SQL線程則可以根據需要做到庫級并行,表級并行,事務級并行。庫級并行在mysql官方版本5.6已經實作。如下圖,并行複制架構實際包含了一個協調線程和若幹個工作線程,協調線程負責分發和解決沖突,工作線程隻負責執行。圖中,DB1,DB2和DB3的事務就可以并發執行,提高了複制的性能。有時候庫級并發可能不夠,需要做表級并發,或更細粒度的事務級并發。

MySQL 複制原理詳解

并行複制如何處理沖突?并發的世界是美好的,但不能亂并發,否則資料就亂了。Master上面通過鎖機制來保證并發的事務有序進行,那麼并行複制呢?Slave必需保證回放的順序與Master上事務執行順序一緻,是以隻要做到順序讀取binlog,将不沖突的事務并發執行即可。對于庫級并發而言,協調線程要保證執行同一個庫的事務放在一個工作線程串行執行;對于表級并發而言,協調線程要保證同一個表的事務串行執行;對于事務級而言,則是保證操作同一行的事務串行執行。

是否粒度越細,性能越好?這個并不是一定的。相對于串行複制而言,并行複制多了一個協調線程。協調線程一個重要作用是解決沖突,粒度越細的并發,可能會有更多的沖突,最終可能也是串行執行的,但消耗了大量的沖突檢測代價。

外部引用 : 《高性能MYSQL》