在談這個特性以前,咱們先來看看MySQL的複製架構衍生史。html
在2000年,MySQL 3.23.15版本引入了Replication。Replication做爲一種準實時同步方式,獲得普遍應用。這個時候的Replicaton的實現涉及到兩個線程,一個在Master,一個在Slave。Slave的I/O和SQL功能是做爲一個線程,從Master獲取到event後直接apply,沒有relay log。這種方式使得讀取event的速度會被Slave replay速度拖慢,當主備存在較大延遲時候,會致使大量binary log沒有備份到Slave端。mysql
在2002年,MySQL 4.0.2版本將Slave端event讀取和執行獨立成兩個線程(IO線程和SQL線程),同時引入了relay log。IO線程讀取event後寫入relay log,SQL線程從relay log中讀取event而後執行。這樣即便SQL線程執行慢,Master的binary log也會盡量的同步到Slave。當Master宕機,切換到Slave,不會出現大量數據丟失。sql
在2010年MySQL 5.5版本以前,一直採用的是這種異步複製的方式。主庫的事務執行不會管備庫的同步進度,若是備庫落後,主庫不幸crash,那麼就會致使數據丟失。因而在MySQL在5.5中就順其天然地引入了半同步複製,主庫在應答客戶端提交的事務前須要保證至少一個從庫接收並寫到relay log中。那麼半同步複製是否能夠作到不丟失數據呢?下面分析。shell
在2016年,MySQL在5.7.17中引入了一個全新的技術,稱之爲InnoDB Group Replication。目前官方MySQL 5.7.17基於Group replication的全同步技術已經問世,全同步技術帶來了更多的數據一致性保障。相信是將來同步技術一個重要方向,值得期待。MySQL 5.7 Group Replication數據庫
根據上面提到的這幾種複製協議,分別對應MySQL幾種複製類型,分別是異步、半同步、全同步。數組
咱們今天談論第二種架構。咱們知道,普通的replication,即MySQL的異步複製,依靠MySQL二進制日誌也即binary log進行數據複製。好比兩臺機器,一臺主機(master),另一臺是從機(slave)。安全
1)正常的複製爲:事務一(t1)寫入binlog buffer;dumper線程通知slave有新的事務t1;binlog buffer進行checkpoint;slave的io線程接收到t1並寫入到本身的的relay log;slave的sql線程寫入到本地數據庫。 這時,master和slave都能看到這條新的事務,即便master掛了,slave能夠提高爲新的master。服務器
2)異常的複製爲:事務一(t1)寫入binlog buffer;dumper線程通知slave有新的事務t1;binlog buffer進行checkpoint;slave由於網絡不穩定,一直沒有收到t1;master掛掉,slave提高爲新的master,t1丟失。網絡
3)很大的問題是:主機和從機事務更新的不一樣步,就算是沒有網絡或者其餘系統的異常,當業務併發上來時,slave由於要順序執行master批量事務,致使很大的延遲。session
爲了彌補以上幾種場景的不足,MySQL從5.5開始推出了半同步複製。相比異步複製,半同步複製提升了數據完整性,由於很明確知道,在一個事務提交成功以後,這個事務就至少會存在於兩個地方。即在master的dumper線程通知slave後,增長了一個ack(消息確認),便是否成功收到t1的標誌碼,也就是dumper線程除了發送t1到slave,還承擔了接收slave的ack工做。若是出現異常,沒有收到ack,那麼將自動降級爲普通的複製,直到異常修復後又會自動變爲半同步複製。
半同步複製具體特性:
半同步複製潛在問題:
先看一下半同步複製原理圖,以下:
master將每一個事務寫入binlog(sync_binlog=1),傳遞到slave刷新到磁盤(sync_relay=1),同時主庫提交事務(commit)。master等待slave反饋收到relay log,只有收到ACK後master纔將commit OK結果反饋給客戶端。
在MySQL 5.5~5.6使用after_commit的模式下,客戶端事務在存儲引擎層提交後,在獲得從庫確認的過程當中,主庫宕機了。此時,即主庫在等待Slave ACK的時候,雖然沒有返回當前客戶端,但事務已經提交,其餘客戶端會讀取到已提交事務。若是Slave端尚未讀到該事務的events,同時主庫發生了crash,而後切換到備庫。那麼以前讀到的事務就不見了,出現了幻讀。以下圖所示,圖片引自Loss-less Semi-Synchronous Replication on MySQL 5.7.2。
若是主庫永遠啓動不了,那麼實際上在主庫已經成功提交的事務,在從庫上是找不到的,也就是數據丟失了,這是MySQL不肯意看到的。因此在MySQL 5.7版本中增長了after_sync(無損複製)參數,並將其設置爲默認半同步方式,解決了數據丟失的問題。
具體完整配置可參考:MySQL基於日誌點作主從複製(二)
Master配置
1)安裝半同步模塊並啓動(此模塊就在/usr/local/mysql/lib/plugin/semisync_master.so)
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';
mysql> set global rpl_semi_sync_master_enabled = 1; mysql> set global rpl_semi_sync_master_timeout = 2000;
安裝後啓動和定製主從鏈接錯誤的超時時間默認是10s可改成2s,一旦有一次超時自動降級爲異步。(以上內容要想永久有效須要寫到配置文件中)
[root@localhost ~]# cat /etc/my.cnf [mysqld] rpl_semi_sync_master_enabled = 1; rpl_semi_sync_master_timeout = 2000;
Slave配置
1)安裝半同步模塊並啓動
mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so'; mysql> set global rpl_semi_sync_slave_enabled = 1; mysql> show global variables like '%semi%'; +---------------------------------+-------+ | Variable_name | Value | +---------------------------------+-------+ | rpl_semi_sync_slave_enabled | ON | | rpl_semi_sync_slave_trace_level | 32 | +---------------------------------+-------+ 2 rows in set (0.00 sec)
2)從節點須要從新鏈接主服務器半同步纔會生效
mysql> stop slave io_thread; mysql> start slave io_thread;
PS:若是想卸載異步模塊就使用uninstall便可。
Master上查看是否啓用了半同步
如今半同步已經正常工做了,主要看Rpl_semi_sync_master_clients是否不爲0,Rpl_semi_sync_master_status是否爲ON。若是Rpl_semi_sync_master_status爲OFF,說明出現了網絡延遲或Slave IO線程延遲。
那麼能夠驗證一下半同步超時,是否會自動降爲異步工做。能夠在Slave上停掉半同步協議,而後在Master上建立數據庫看一下能不能複製到Slave上。
Slave
# 關閉半同步; mysql> set global rpl_semi_sync_slave_enabled = 0 ; mysql> stop slave io_thread; mysql> start slave io_thread;
Master
mysql> create database dbtest; Query OK, 1 row affected (2.01 sec)
mysql> create database dbtest01; Query OK, 1 row affected (0.01 sec)
建立第一個數據庫花了2.01秒,而咱們前面設置的超時時間是2秒,而建立第二個數據庫花了0.01秒,由此得出結論是超時轉換爲異步傳送。能夠在Master上查看半同步相關的參數值Rpl_semi_sync_master_clients和Rpl_semi_sync_master_status是否正常。
mysql> show global status like '%semi%'; +--------------------------------------------+-----------+ | Variable_name | Value | +--------------------------------------------+-----------+ | Rpl_semi_sync_master_clients | 0 | | Rpl_semi_sync_master_net_avg_wait_time | 0 | | Rpl_semi_sync_master_net_wait_time | 0 | | Rpl_semi_sync_master_net_waits | 37490 | | Rpl_semi_sync_master_no_times | 3 | | Rpl_semi_sync_master_no_tx | 197542 | | Rpl_semi_sync_master_status | OFF | | Rpl_semi_sync_master_timefunc_failures | 0 | | Rpl_semi_sync_master_tx_avg_wait_time | 51351 | | Rpl_semi_sync_master_tx_wait_time | 362437445 | | Rpl_semi_sync_master_tx_waits | 7058 | | Rpl_semi_sync_master_wait_pos_backtraverse | 0 | | Rpl_semi_sync_master_wait_sessions | 0 | | Rpl_semi_sync_master_yes_tx | 7472 | +--------------------------------------------+-----------+ 14 rows in set (0.00 sec)
能夠看到都自動關閉了,須要注意一點的是,當Slave開啓半同步後,或者當主從之間網絡延遲恢復正常的時候,半同步複製會自動從異步複製又轉爲半同步複製,仍是至關智能的。
另外我的在實際使用中還碰到一種狀況從庫IO線程有延遲時,主庫會自動把半同步複製降爲異步複製;當從庫IO延遲沒有時,主庫又會把異步複製升級爲半同步複製。能夠進行壓測模擬,可是此時查看Master的狀態跟上面直接關閉Slave半同步有些不一樣,會發現Rpl_semi_sync_master_clients仍然等於1,而Rpl_semi_sync_master_status等於OFF。
隨着MySQL 5.7版本的發佈,半同步複製技術升級爲全新的Loss-less Semi-Synchronous Replication架構,其成熟度、數據一致性與執行效率獲得顯著的提高。
如今咱們已經知道,在半同步環境下,主庫是在事務提交以後等待Slave ACK,因此纔會有數據不一致問題。因此這個Slave ACK在什麼時間去等待,也是一個很關鍵的問題了。所以MySQL針對半同步複製的問題,在5.7.2引入了Loss-less Semi-Synchronous,在調用binlog sync以後,engine層commit以前等待Slave ACK。這樣只有在確認Slave收到事務events後,事務纔會提交。在commit以前等待Slave ACK,同時能夠堆積事務,利於group commit,有利於提高性能。
MySQL 5.7安裝半同步模塊,命令以下:
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so'; Query OK, 0 rows affected (0.00 sec)
看一下相關狀態信息
mysql> show global variables like '%semi%'; +-------------------------------------------+------------+ | Variable_name | Value | +-------------------------------------------+------------+ | rpl_semi_sync_master_enabled | OFF | | rpl_semi_sync_master_timeout | 10000 | | rpl_semi_sync_master_trace_level | 32 | | rpl_semi_sync_master_wait_for_slave_count | 1 | | rpl_semi_sync_master_wait_no_slave | ON | | rpl_semi_sync_master_wait_point | AFTER_SYNC | +-------------------------------------------+------------+ 6 rows in set (0.00 sec)
在Loss-less Semi-Synchronous模式下,master在調用binlog sync以後,engine層commit以前等待Slave ACK(須要收到至少一個Slave節點回復的ACK後)。這樣只有在確認Slave收到事務events後,master事務纔會提交,而後把結果返回給客戶端。此時此事務纔對其餘事務可見。在這種模式下解決了after_commit模式帶來的幻讀和數據丟失問題,由於主庫沒有提交事務。但也會有個問題,假設主庫在存儲引擎提交以前掛了,那麼很明顯這個事務是不成功的,但因爲對應的Binlog已經作了Sync操做,從庫已經收到了這些Binlog,而且執行成功,至關於在從庫上多了數據,也算是有問題的,但多了數據,問題通常不算嚴重。這個問題能夠這樣理解,做爲MySQL,在沒辦法解決分佈式數據一致性問題的狀況下,它能保證的是不丟數據,多了數據總比丟數據要好。
無損複製其實就是對semi sync增長了rpl_semi_sync_master_wait_point參數,來控制半同步模式下主庫在返回給會話事務成功以前提交事務的方式。rpl_semi_sync_master_wait_point該參數有兩個值:AFTER_COMMIT和AFTER_SYNC
第一個值:AFTER_COMMIT(5.6默認值)
master將每一個事務寫入binlog(sync_binlog=1),傳遞到slave刷新到磁盤(sync_relay=1),同時主庫提交事務。master等待slave反饋收到relay log,只有收到ACK後master纔將commit OK結果反饋給客戶端。
第二個值:AFTER_SYNC(5.7默認值,但5.6中無此模式)
master將每一個事務寫入binlog , 傳遞到slave刷新到磁盤(relay log)。master等待slave反饋接收到relay log的ack以後,再提交事務而且返回commit OK結果給客戶端。 即便主庫crash,全部在主庫上已經提交的事務都能保證已經同步到slave的relay log中。
半同步複製與無損複製的對比
1.1 ACK的時間點不一樣
1.2 主從數據一致性
所以5.7引入了無損複製(after_sync)模式,帶來的主要收益是解決after_commit致使的master crash後數據丟失問題,所以在引入after_sync模式後,全部提交的數據已經都被複制,故障切換時數據一致性將獲得提高。
舊版本的semi sync受限於dump thread ,緣由是dump thread承擔了兩份不一樣且又十分頻繁的任務:傳送binlog給slave ,還須要等待slave反饋信息,並且這兩個任務是串行的,dump thread必須等待slave返回以後纔會傳送下一個events事務。dump thread已然成爲整個半同步提升性能的瓶頸。在高併發業務場景下,這樣的機制會影響數據庫總體的TPS 。
爲了解決上述問題,在5.7版本的semi sync框架中,獨立出一個Ack Receiver線程 ,專門用於接收slave返回的ack請求,這將以前dump線程的發送和接受工做分爲了兩個線程來處理。這樣master上有兩個線程獨立工做,能夠同時發送binlog到slave,和接收slave的ack信息。所以半同步複製獲得了極大的性能提高。這也是MySQL 5.7發佈時號稱的Faster semi-sync replication。
可是在MySQL 5.7.17以前,這個Ack Receiver線程採用了select機制來監聽slave返回的結果,然而select機制監控的文件句柄只能是0-1024,當超過1024時,用戶在MySQL的錯誤日誌中或許會收到相似以下的報錯,更有甚者會致使MySQL發生宕機。
semi-sync master failed on net_flush() before waiting for slave reply.
MySQL 5.7.17版本開始,官方修復了這個bug,開始使用poll機制來替換原來的select機制,從而能夠避免上面的問題。其實poll調用本質上和select沒有區別,只是在I/O句柄數理論上沒有上限了,緣由是它是基於鏈表來存儲的。可是一樣有缺點:好比大量的fd的數組被總體複製於用戶態和內核地址空間之間,而無論這樣的複製是否是有意義。poll還有一個特色是「水平觸發」,若是報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。
其實在高性能軟件中都是用另一種調用機制,名爲epoll,高性能的表明,好比Nginx,haproxy等都是使用epoll。可能poll的複雜性比epoll低,另外對於ack receiver線程來講可能poll足矣。
MySQL 5.7新增了rpl_semi_sync_master_wait_slave_count參數,能夠用來控制主庫接受多少個slave寫事務成功反饋,給高可用架構切換提供了靈活性。如圖所示,當count值爲2時,master需等待兩個slave的ack。
舊版本半同步複製在主提交binlog的寫會話和dump thread讀binlog的操做都會對binlog添加互斥鎖,致使binlog文件的讀寫是串行化的,存在併發度的問題。
MySQL 5.7對binlog lock進行了如下兩方面優化:
1. 移除了dump thread對binlog的互斥鎖。
2. 加入了安全邊際保證binlog的讀安全。
能夠看到從replication功能引入後,官方MySQL一直在不停的完善,前進。同時咱們能夠發現當前原生的MySQL主備複製實現實際上很難在知足數據一致性的前提下作到高可用、高性能。
sync_binlog的配置
其實無損複製流程中也會存在着會致使主備數據不一致的狀況,使主備同步失敗的情形。見下面sync_binlog配置的分析。
源碼剖析
sql/binlog.cc ordered_commit 9002 update_binlog_end_pos_after_sync= (get_sync_period() == 1); ... //當sync_period(sync_binlog)爲1時,在sync以後update binlog end pos 9021 if (!update_binlog_end_pos_after_sync) //更新binlog end position,dump線程會發送更新後的events 9022 update_binlog_end_pos(); ... // 9057 std::pair<bool, bool> result= sync_binlog_file(false); ... // 9061 if (update_binlog_end_pos_after_sync) 9062 { ... 9068 update_binlog_end_pos(tmp_thd->get_trans_pos()); 9069 } sql/binlog.cc sync_binlog_file 8618 std::pair<bool, bool> 8619 MYSQL_BIN_LOG::sync_binlog_file(bool force) 8620 { 8621 bool synced= false; 8622 unsigned int sync_period= get_sync_period(); //sync_binlog值 //sync_period爲0不作sync操做,其餘值爲達到sync調用次數後sync 8623 if (force || (sync_period && ++sync_counter >= sync_period)) 8624 {
配置分析
當sync_binlog爲0的時候,binlog sync磁盤由操做系統負責。當不爲0的時候,其數值爲按期sync磁盤的binlog commit group數。經過源碼咱們知道,sync_binlog值不等於1的時候事務在FLUSH階段就傳輸binlog到從庫了,而值爲1時,binlog同步操做是在SYNC階段後。當sync_binlog值大於1的時候,sync binlog操做可能並無使binlog落盤。若是沒有落盤,事務在提交前,Master掉電,而後恢復,那麼這個時候該事務被回滾。可是Slave上可能已經收到了該事務的events而且執行,這個時候就會出現Slave事務比Master多的狀況,主備同步會失敗。因此若是要保持主備一致,須要設置sync_binlog爲1。
WAIT_AFTER_SYNC和WAIT_AFTER_COMMIT兩圖中Send Events的位置,也可能致使主備數據不一致,出現同步失敗的情形。實際在rpl_semi_sync_master_wait_point分析的圖中是sync binlog大於1的狀況。根據上面源碼,流程以下圖所示。Master依次執行flush binlog, update binlog position, sync binlog。若是Master在update binlog position後,sync binlog前掉電,Master再次啓動後原事務就會被回滾。但可能出現Slave獲取到Events,這也會致使Slave數據比Master多,主備同步失敗。
因爲上面的緣由,sync_binlog設置爲1的時候,MySQL會update binlog end pos after sync。流程以下圖所示。這時候,對於每個事務都須要sync binlog,同時sync binlog和網絡發送events會是一個串行的過程,性能降低明顯。
sync_relay_log的配置
源碼剖析
sql/rpl_slave.cc handle_slave_io 5764 if (queue_event(mi, event_buf, event_len)) ... 5771 if (RUN_HOOK(binlog_relay_io, after_queue_event, 5772 (thd, mi, event_buf, event_len, synced))) after_queue_event ->plugin/semisync/semisync_slave_plugin.cc repl_semi_slave_queue_event ->plugin/semisync/semisync_slave.cc ReplSemiSyncSlave::slaveReply queue_event ->sql/binlog.cc MYSQL_BIN_LOG::append_buffer(const char* buf, uint len, Master_info *mi) ->sql/binlog.cc after_append_to_relay_log(mi); ->sql/binlog.cc flush_and_sync(0) ->sql/binlog.cc sync_binlog_file(force)
配置分析
在Slave的IO線程中get_sync_period得到的是sync_relay_log的值,與sync_binlog對sync控制同樣。當sync_relay_log不是1的時候,semisync返回給Master的position可能沒有sync到磁盤。在gtid_mode下,在保證前面兩個配置正確的狀況下,sync_relay_log不是1的時候,僅發生Master或Slave的一次Crash並不會發生數據丟失或者主備同步失敗狀況。若是發生Slave沒有sync relay log,Master端事務提交,客戶端觀察到事務提交,而後Slave端Crash。這樣Slave端就會丟失掉已經回覆Master ACK的事務events。
但當Slave再次啓動,若是沒有來得及從Master端同步丟失的事務Events,Master就Crash。這個時候,用戶訪問Slave就會發現數據丟失。
經過上面這個Case,MySQL semisync若是要保證任意時刻發生一臺機器宕機都不丟失數據,須要同時設置sync_relay_log爲1。對relay log的sync操做是在queue_event中,對每一個event都要sync,因此sync_relay_log設置爲1的時候,事務響應時間會受到影響,對於涉及數據比較多的事務延遲會增長不少。
MySQL三節點
在一主一從的主備semisync的數據一致性分析中放棄了高可用,當主備之間網絡抖動或者一臺宕機的狀況下中止提供服務。要作到高可用,很天然咱們能夠想到一主兩從,這樣解決某一網絡抖動或一臺宕機時候的可用性問題。可是,前文敘述要保證數據一致性配置要求依然存在,即正常狀況下的性能不會有改善。同時須要解決Master宕機時候,如何選取新主機的問題,如何避免多主的情形。
選取新主機時必定要讀取兩個從機,看哪個從機有最新的日誌,不然可能致使數據丟失。這樣的三節點方案就相似分佈式Quorum機制,寫的時候須要保證寫成功三節點中的法定集合,肯定新主的時候須要讀取法定集合。利用分佈式一致性協議Paxos/Raft能夠解決數據一致性問題,選主問題和多主問題,所以近些年,國內數據庫團隊大多實現了基於Paxos/Raft的三節點方案。近來MySQL官方也以插件形式引入了支持多主集羣的Group Replication方案。
轉自:http://www.ywnds.com/?p=7023