MySQL crash-safe replication

MySQL數據庫的成功離不開其replicaiton,相對於Oracle DG和Microsoft SQL Server Log Shipping來說,其簡單易上手,基本上1,2分鐘內根據手冊就能完成環境的搭建。然而,隨着使用的深入,replication自身的問題會慢慢顯露,其中非crash safe的特性使得許多DBA感到頭疼,甚至不能理解其所發問題的原因。簡單來說,crash-safe replication是指當master/slave任何一個節點發生宕機等意外情況下,服務器重啓後master/slave的數據依然能夠保證一致性。

 

crash-safe master相對比較簡單,只要使用事務的存儲引擎,並且正確的配置就能達到crash safe的效果。對於最爲常見的InnoDB存儲引擎而言,只需在配置文件中進行如下的設置:

sync_binlog=l
innodb-flush-log-at-trx-commit=1

MySQL 5.6版本之前存在一個bug,即當啓用上述兩個參數時,會使得InnoDB存儲引擎的group commit失效,從而導致在寫密集的環境中性能的急劇下降。因此,DBA在性能和數據一致性中做了妥協,通常將參數innodb-flush-log-at-trx-commit設置爲2,而這就導致了master不再是crash safe的,主從數據可能會不一致。MariaDB真正解決了該問題,因此很多分支版本,比如Percona,

Facebook MySQL,InnoSQL都將MariaDB的group commit方案移植到了自己的分支中,從而解決group commit失效的問題。

 

crash-safe slave的情況就有些複雜,而這可能是DBA更爲常見的問題。例如slave不斷的報1062錯誤,或者發現主從數據不一致(特別是表沒有主鍵的情況)。而這時DBA的選擇通常也很無奈,基本就是全庫重建了。所以說,當你有運維超過200臺以上的MySQL服務器的經驗時,就會發現這是一個很大的問題。

 

導致不能實現crash-safe slave有兩方面的原因,即replication中的SQL thread和IO thread。首先來看SQL thread,其主要完成兩個操作:

  • 運行relay log中對應的事務信息

  • 更新relay-info.log文件

更新relay-info.log文件是爲了記錄已經執行relay log中的位置,當slave重啓後可以根據這個位置繼續同步relay log。但是,這裏用戶會發現這兩個操作不是在一個事務中,一個是數據庫操作,一個是文件操作,因此不能達到原子的效果。此外,MySQL數據庫默認對於文件relay-info.log是寫入到操作系統緩存,因此在發生宕機時可能導致大量的已更新位置的丟失,從而導致重複執行SQL語句,最終的現象就是主從數據不一致。MySQL 5.5新增了參數sync_relay_log_info,可以控制每次事務更新relay-info.log後就進行一次fdatasync操作,這加重了系統負擔,而且即使這樣也可能存在最後一個事務丟失的情況。

 

早在MySQL 4.0時Google就發佈過補丁解決過該問題(https://code.google.com/p/google-mysql-tools/wiki/TransactionalReplication),其在每次InnoDB存儲引擎提交時,記錄二進制日誌的位置信息到事務系統段的段頭。當slave重啓後將保存的這部分的信息重新生成relay-info.log文件。Percona也採用了這種方式,並通過參數innodb_recovery_update_relay_log來控制是否在啓動時替換relay-info.log文件。

 

MySQL 5.6採用了另一種方法,就是將relay-info.log的信息保存在InnoDB的事務表中,這時兩個操作都是數據庫操作,在一個事務中就能得到原子性。例如對於slave的日誌回放,其過程爲:

BEGIN;
apply log event;
apply log event; 
UPDATE mysql.slave_relay_log_info    SET Master_log_pos = Exec_Master_Log_Pos,
    Master_log_name = Relay_Master_Log_File,
    Relay_log_name = Relay_Log_File,
    Relay_log_pos = Relay_Log_Pos;COMMIT

這樣的處理方式解決更新relay-info.log的原子性問題,但是這是最終完美的解決方案嗎?很可惜,還是存在一些缺陷。可以看到由於最後表slave_relay_log_info的更新會鎖住記錄,從而導致slave上的事務提交都是串行的。雖然MySQL 5.6支持並行複製,但是由於串行更新表slave_relay_log_info,再次導致group commit失效。因此通過--log-slave-updates再立級聯replication的話,性能又會受限。MariaDB正在解決該問題,非常有可能在MariaDB 10 GA版本中見到完美的解決方案。

 

IO thread用於同步master上的二進制日誌,但是其在crash時依然會導致數據不一致的情況發生。IO thread將收到的二進制日誌寫入到relay log,每個二進制日誌由多個log event組成,所以每接受到一個log event就需要更新master-info.log。和relay-info.log一樣,其也是寫入操作系統緩存,參數sync_master_info可以控制fdatasync的時間。由於IO thread的更新不能像SQL thread一樣進行放到一個事務進行原子操作,因此其是對數據一致性會產生影響,設想一個log event傳送到了relay log中兩次的情形。

 

不過好在從MySQL 5.5版本開始提供了參數relay_log_recovery,當發生crash導致重連master時,其不根據master-info.log的信息進行重連,而是根據relay-info中執行到master的位置信息重新開始拉master上的日誌數據(不過需要確保日誌依然存在於master上,否則就。。。)

 

crash-safe是運維人員不能忽略的一點,否則DBA將忙於處理這些異常狀況導致的slave服務停止的情形。MySQL 5.6雖然提供了crash safe的解決方案,但依然存在一些不完美的可能性。所以,小夥伴們,讓我們期待MariaDB 10 GA版本的發佈吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章