標籤:unsigned counter star 建立 avg creat 架構 shell 日誌
一、複製架構衍生史
在談這個特性之前,我們先來看看MySQL的複製架構衍生史。
在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端。
在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,不會出現大量資料丟失。
在2010年MySQL 5.5版本之前,一直採用的是這種非同步複製的方式。主庫的事務執行不會管備庫的同步進度,如果備庫落後,主庫不幸crash,那麼就會導致資料丟失。於是在MySQL在5.5中就順其自然地引入了半同步複製,主庫在應答用戶端提交的事務前需要保證至少一個從庫接收並寫到relay log中。那麼半同步複製是否可以做到不遺失資料呢?下面分析。
在2016年,MySQL在5.7.17中引入了一個全新的技術,稱之為InnoDB Group Replication。目前官方MySQL 5.7.17基於Group replication的全同步技術已經問世,全同步技術帶來了更多的資料一致性保障。相信是未來同步技術一個重要方向,值得期待。MySQL 5.7 Group Replication
根據上面提到的這幾種複製協議,分別對應MySQL幾種複製類型,分別是非同步、半同步、全同步。
- 對於非同步複製,主庫將事務Binlog事件寫入到Binlog檔案中,此時主庫只會通知一下Dump線程發送這些新的Binlog,然後主庫就會繼續處理提交操作,而此時不會保證這些Binlog傳到任何一個從庫節點上。
- 對於全同步複製,當主庫提交事務之後,所有的從庫節點必須收到,APPLY並且提交這些事務,然後主庫線程才能繼續做後續操作。這裡面有一個很明顯的缺點就是,主庫完成一個事務的時間被拉長,效能降低。
- 對於半同步複製,是介於全同步複製和非同步複製之間的一種,主庫只需要等待至少一個從庫節點收到並且Flush Binlog到Relay Log檔案即可,主庫不需要等待所有從庫給主庫反饋。同時,這裡只是一個收到的反饋,而不是已經完全執行並且提交的反饋,這樣就節省了很多時間。
二、半同步複製技術
我們今天談論第二種架構。我們知道,普通的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批量事務,導致很大的延遲。
為了彌補以上幾種情境的不足,MySQL從5.5開始推出了半同步複製。相比非同步複製,半同步複製提高了資料完整性,因為很明確知道,在一個事務提交成功之後,這個事務就至少會存在於兩個地方。即在master的dumper線程通知slave後,增加了一個ack(訊息確認),即是否成功收到t1的標誌碼,也就是dumper線程除了發送t1到slave,還承擔了接收slave的ack工作。如果出現異常,沒有收到ack,那麼將自動降級為普通的複製,直到異常修複後又會自動變為半同步複製。
半同步複製具體特性:
- 從庫會在串連到主庫時告訴主庫,它是不是配置了半同步。
- 如果半同步複製在主庫端是開啟了的,並且至少有一個半同步複製的從庫節點,那麼此時主庫的事務線程在提交時會被阻塞並等待,結果有兩種可能,要麼至少一個從庫節點通知它已經收到了所有這個事務的Binlog事件,要麼一直等待直到超過配置的某一個時間點為止,而此時,半同步複製將自動關閉,轉換為非同步複製。
- 從庫節點只有在接收到某一個事務的所有Binlog,將其寫入並Flush到Relay Log檔案之後,才會通知對應主庫上面的等待線程。
- 如果在等待過程中,等待時間已經超過了配置的逾時時間,沒有任何一個從節點通知當前事務,那麼此時主庫會自動轉換為非同步複製,當至少一個半同步從節點趕上來時,主庫便會自動轉換為半同步方式的複製。
- 半同步複製必須是在主庫和從庫兩端都開啟時才行,如果在主庫上沒開啟,或者在主庫上開啟了而在從庫上沒有開啟,主庫都會使用非同步方式複製。
半同步複製潛在問題:
先看一下半同步複製原理圖,如下:
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 5.6半同步複製配置
具體完整配置可參考: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,一旦有一次逾時自動降級為非同步。(以上內容要想永久有效需要寫到設定檔中)
[[email protected] ~]# 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架構,其成熟度等級、資料一致性與執行效率得到顯著的提升。
四、MySQL 5.7半同步複製的改進
現在我們已經知道,在半同步環境下,主庫是在事務提交之後等待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)
在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的時間點不同
- 半同步複製在InnoDB層的Commit Log後等待ACK,主從切換會有資料丟失風險。
- 無損複製在MySQL Server層的Write binlog後等待ACK,主從切換會有資料變多風險。
1.2 主從資料一致性
- 半同步複製意味著在Master節點上,這個剛剛提交的事物對資料庫的修改,對其他事物是可見的。因此,如果在等待Slave ACK的時候crash了,那麼會對其他事務出現幻讀,資料丟失。
- 無損複製在write binlog完成後,就傳輸binlog,但還沒有去寫commit log,意味著當前這個事物對資料庫的修改,其他事物也是不可見的。因此,不會出現幻讀,資料丟失風險。
因此5.7引入了無損複製(after_sync)模式,帶來的主要收益是解決after_commit導致的master crash後資料丟失問題,因此在引入after_sync模式後,所有提交的資料已經都被複製,故障切換時資料一致性將得到提升。
- 效能提升,支援發送binlog和接受ack的非同步化
舊版本的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足矣。
- 效能提升,控制主庫接收slave寫事務成功反饋數量
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_relay與半同步複製
sync_binlog的配置
其實無損複製流程中也會存在著會導致主備資料不一致的情況,使主備同步失敗的情形。見下面sync_binlog配置的分析。
源碼剖析
sql/binlog.cc ordered_commit9002 update_binlog_end_pos_after_sync= (get_sync_period() == 1); ... //當sync_period(sync_binlog)為1時,在sync之後update binlog end pos9021 if (!update_binlog_end_pos_after_sync) //更新binlog end position,dump線程會發送更新後的events9022 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_file8618 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調用次數後sync8623 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
MySQL 5.7半同步複製技術