今天我們聊聊復制,復制對於mysql的重要性不言而喻,mysql集群的負載均衡,讀寫分離和高可用都是基於復制實現。下文主要從4個方面展開,mysql的異步復制,半同步復制和並行復制,最后會簡單聊下第三方復制工具。由於生產環境中,innodb存儲引擎支持事務,並且行級復制使用廣泛,所以下文的討論都是基於這種假設。
異步復制
異步復制是mysql自帶的最原始的復制方式,主庫和備庫成功建立起復制關系后,在備庫上會有一個IO線程去主庫拉取binlog,並將binlog寫到本地,就是圖1中的Relay log,然后備庫會開啟另外一個SQL線程去讀取回放Relay log,通過這種方式達到Master-Slave數據同步的目的。通常情況下,Slave是只讀的,可以承擔一部分讀流量,而且可以根據實際需要,添加一個或多個Slave,這樣在一定程度上可以緩解主庫的讀壓力;另一方面,若Master出現異常(crash,硬件故障等),無法對外提供服務,此時Slave可以承擔起Master的重任,避免了單點的產生,所以復制就是為容災和提高性能而生。
圖1
半同步復制
一般情況下,異步復制就已經足夠應付了,但由於是異步復制,備庫極有可能是落后於主庫,特別是極端情況下,我們無法保證主備數據是嚴格一致的(即使我們觀察到Seconds Behind Master 這個值為0)。比如,當用戶發起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模式,如圖2,Start和End分別表示用戶發起Commit命令和Master返回給用戶的時間點,中間部分就是整個Commit過程Master和Slave做的事情。
圖2
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的強一致性。圖3是AFTER_SYNC模式,事務提交過程。
圖3
並行復制
半同步復制解決了Master-Slave的強一致問題,那么性能問題呢?從圖1中可以看到參與復制的主要有兩個線程:IO線程和SQL線程,分別用於拉取和回放binlog。對於Slave而言,所有拉取和解析binlog的動作都是串行的,相對於Master並發處理用戶請求,在高負載下, 若Master產生binlog的速度超過Slave消費binlog的速度,導致Slave出現延遲。如圖4,可以看到,Users和Master之間的管道遠遠大於Master和Slave之間的管道。
圖4
那么如何並行化,並行IO線程,還是並行SQL線程?其實兩方面都可以並行,但是並行SQL線程的收益更大,因為SQL線程做的事情更多(解析,執行)。並行IO線程,可以將從Master拉取和寫Relay log分為兩個線程;並行SQL線程則可以根據需要做到庫級並行,表級並行,事務級並行。庫級並行在mysql官方版本5.6已經實現。如圖5,並行復制框架實際包含了一個協調線程和若干個工作線程,協調線程負責分發和解決沖突,工作線程只負責執行。圖中,DB1,DB2和DB3的事務就可以並發執行,提高了復制的性能。有時候庫級並發可能不夠,需要做表級並發,或更細粒度的事務級並發。
圖 5
並行復制如何處理沖突?並發的世界是美好的,但不能亂並發,否則數據就亂了。Master上面通過鎖機制來保證並發的事務有序進行,那么並行復制呢?Slave必需保證回放的順序與Master上事務執行順序一致,因此只要做到順序讀取binlog,將不沖突的事務並發執行即可。對於庫級並發而言,協調線程要保證執行同一個庫的事務放在一個工作線程串行執行;對於表級並發而言,協調線程要保證同一個表的事務串行執行;對於事務級而言,則是保證操作同一行的事務串行執行。
是否粒度越細,性能越好?這個並不是一定的。相對於串行復制而言,並行復制多了一個協調線程。協調線程一個重要作用是解決沖突,粒度越細的並發,可能會有更多的沖突,最終可能也是串行執行的,但消耗了大量的沖突檢測代價。
第三方復制工具
為什么會出現第三方復制工具?第三方復制工具的出現一定是內嵌的復制功能不能滿足用戶需求,就像半同步復制和並行復制從無到有一樣。既然現在mysql復制已經做地這么好了,為什么還有第三方復制工具,我能想到最重要的一點是異構復制。在異構數據源遷移場景下,內嵌復制是無能為力的,第三方復制工具通過解析源端的數據庫日志,然后在目的端回放,就能達到同步的目的,比如大名鼎鼎的GoldenGate就是一個例子。第三方復制工具同樣能很好地實現並發,在並行復制出現之前,這也是一個巨大的優勢。另一方面,就是可以統一下游,避免所有下游都跑到DB上拉binlog,增大DB負載。
參考文檔
https://code.google.com/p/google-mysql-tools/wiki/SemiSyncReplicationDesign
http://yoshinorimatsunobu.blogspot.com/2014/04/semi-synchronous-replication-at-facebook.html
http://mysqllover.com/?s=semi
https://code.google.com/p/mysql-parallel-replication/
http://www.cnblogs.com/cchust/p/3295547.html
http://www.gpfeng.com/?p=515
http://wenku.baidu.com/link?url=wc8ZY0j1oIRIRmcZ-dYFTcWghsR2nrxW9CqdXQczeqUqogi1GU6uF48nNjU_LraUmPxmxKU2kcsfYhPMTdTW-I65N3owFVuAOdOufPklb2a
http://www.docin.com/p-450229355.html