MySQL的復制是基於binlog來實現的。
流程如下
涉及到三個線程,主庫的DUMP線程,從庫的IO線程和SQL線程。
1. 主庫將所有操作都記錄到binlog中。當復制開啟時,主庫的DUMP線程根據從庫IO線程的請求將binlog中的內容發送到從庫。
2. 從庫的IO線程接受到主庫DUMP線程發送的binlog事件後,將其寫到本地的relay-log。
3. 從庫的SQL線程重放relay-log中的事件。
實際上,在MySQL 4.0之前,復制只有兩個線程,master和slave端各一個。在Slave端,該線程同時負責接收主庫發來的binlog事件,也負責事件的重放,所以沒有使用relay-log,這樣容易導致,當binlog事件的重放速度較慢時,會影響binlog事件的接受。
復制的搭建
基本步驟如下:
1. 配置主庫和從庫
2. 創建復制的賬號
3. 創建主庫一致性快照
4. 根據主庫的快照,建立從庫
5. 開啟復制
詳細步驟如下
1. 配置主庫和從庫
主庫
開啟binlog並設置server-id
[mysqld] log-bin=mysql-bin server-id=1
在一組復制結構中,每個服務器必須配置一個唯一的server-id。該值的有效范圍為1~232-1。
如果server-id設置為0的話,則MySQL會自動將它更改為1。此時,對復制沒有影響。
如果server-id沒有顯式設置的話,則MySQL同樣會將它設置為1,但是從連接的時候,IO線程會報錯
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Misconfigured master - server_id was not set'
從庫
設置server-id
[mysqld] server-id=2
在從服務器上,可以選擇不開啟binlog。當開啟了binlog後,如果想把重放的時間同樣也記錄到binlog中,可將log_slave_updates參數設置為1。
設置完畢後,重啟數據庫
2. 創建復制賬號
主庫上執行
mysql> CREATE USER 'repl'@'%.mydomain.com' IDENTIFIED BY 'slavepass'; mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%.mydomain.com';
3. 創建主庫一致性快照
在這裡,我用mysqldump來創建數據庫快照
# mysqldump --master-data=2 -R --single-transaction -A > 3306_20160815.sql
在生成的備份文件中,我們可以得到在對數據庫執行一致性快照時主的狀態,包括二進制文件的名稱及位置
# head -30 3306_20160815.sql
... -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000016', MASTER_LOG_POS=120; ...
4. 根據主庫的快照,建立從庫
# mysql < 3306_20160815.sql
5. 開啟復制
根據第3步獲取到的主庫狀態執行CHANGE MASTER TO命令
mysql> CHANGE MASTER TO MASTER_HOST='master_host_name', MASTER_USER='replication_user_name', MASTER_PASSWORD='replication_password', MASTER_LOG_FILE='recorded_log_file_name', MASTER_LOG_POS=recorded_log_position;
在執行CHANGE MASTER TO命令後,從庫並不會連接到主庫,而只是將這些信息寫入到從庫數據目錄下的master.info和relay-log.info中。如果沒有顯式指定MASTER_LOG_FILE,則默認為空,因為此時還沒有和主庫建立連接,並不知道主庫binlog的文件名稱,只有等到和主庫建立連接時才能知道。如果MASTER_LOG_POS沒有顯式指定,則默認為4,即忽略binlog的頭4個字節,從第一個事件開始讀取。
開啟復制功能
mysql> start slave;
查看復制的狀態
mysql> show slave status\G
重點關注以下兩個變量,如果為YES,則代表復制搭建成功。
... Slave_IO_Running: Yes Slave_SQL_Running: Yes ...
注:以上搭建場景是基於主庫上已經有一定的數據。如果在全新的環境中搭建,實際上會更簡單。
只需獲取主庫的狀態信息即可
mysql> show master status; +------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000016 | 120 | | | | +------------------+----------+--------------+------------------+-------------------+ 1 row in set (0.00 sec
MySQL復制的格式
MySQL的復制有三種格式:
Statement-based replication(SBR)
基於語句的復制,即master上執行的SQL語句原封不動的在slave上重放。該復制格式在MySQL 3.23即出現了。
優點:
1. 節省binlog的空間。
2. 可用於審核,畢竟所有的DML語句都是直接記錄在binlog中。
缺點:
1. 很多函數在主從上執行的結果並不一致
LOAD_FILE(),UUID(), UUID_SHORT(),USER(),FOUND_ROWS(),SYSDATE(),GET_LOCK(),IS_FREE_LOCK(),IS_USED_LOCK(),MASTER_POS_WAIT(),RAND(),RELEASE_LOCK(),SLEEP(),VERSION()
2. DELETE和UPDATE操作,帶了LIMIT子句,卻沒有帶ORDER BY,可能導致主從執行的結果並不一致。
3. 相對於基於行的復制,master上執行INSERT ... SELECT操作需要更多的行鎖。
4. 自定義函數(UDF)必須確保執行的結果是確定的。
Row-based replication(RBR)
基於行的復制,MySQL 5.1引入的,相對於SBR,它記錄的是DML操作涉及到的行。
優點:
1. 安全。master上所有的變更都能復制到slave上。
2. 在執行以下操作時,只需更少的行鎖。
INSERT ... SELECT
帶有AUTO_INCREMENT列的INSERT操作
UPDATE和DELETE中,WHERE條件沒有用上索引。
缺點:
1. 會產生大量的日志。
譬如一張表有1w條記錄,如果我不帶任何條件執行delete操作,則在基於statement的復制中,在binlog中只會記錄delete from table這一條記錄,但是在基於row的復制中,則會記錄1w筆記錄,每筆記錄類似於delete from table where ..。
這會帶來以下問題
1> 如果利用binlog進行恢復,會需要更長的時間.
2> 在寫數據到binlog中時,因為數據量大,會導致binlog的鎖定時間較長,影響數據庫的並發。
3> 較大的日志會對磁盤IO和網絡IO產生較大的壓力。
4> 增大slave的延遲。
2. 不會對二進制日志進行校驗。
3. 不推薦基於庫級別的復制
包括--replicate-do-db, --replicate-ignore-db, --replicate-rewrite-db
MIXED
MIXED是上述兩者的的結合,會根據執行的語句和涉及的存儲引擎自動在這兩種模式間切換。默認情況下,采用的是基於語句的復制模式,在遇到unsafe statements時,會切換為基於行的復制模式。
關於切換的條件,可參考官方文檔:http://dev.mysql.com/doc/refman/5.7/en/binary-log-mixed.html
在MySQL 5.7.7之前,默認的是基於語句的復制模式,從MySQL 5.7.7開始,默認基於行的復制模式。
復制中涉及的文件
relay-log
relay-log保存著從庫IO線程從主庫讀取到的binlog事件。和binlog格式一樣,可通過mysqlbinlog解析其中的內容。
可通過配置relay_log和relay_log_index參數設置relay-log和relay-log.index文件的名稱。
relay-log在如下情況下會發生切換:
1. slave IO線程啟動的時候。
2. 執行FLUSH LOGS操作的時候。
3. 達到參數max_relay_log_size設置的大小,默認為0,即以max_binlog_size的值作為max_relay_log_size的大小。
slave SQL線程在重放完一個relay-log文件中的所有事件後,會自動刪除relay-log文件(由relay_log_purge參數控制),所以沒有顯式刪除relay-log的命令。
master.info
該文件保存了主庫的主機名,端口,復制賬號的用戶名和密碼,以及從庫接受主庫binlog事件的位置信息,通過這些位置信息,從庫的IO線程知道下次從哪裡獲取主庫的binlog事件。
該位置信息對應於show slave status\G中的Master_Log_File和Read_Master_Log_Pos。
如下所示:
# cat master.info
23 mysql-bin.000014 120 192.168.244.10 repl repl 3306 60 0 0 1800.000 0 cad449f2-5d4f-11e6-b353-000c29c64704 86400 0
relay-log.info
該文件記錄從庫的重放信息,這樣即便數據庫發生重啟,SQL線程也知道該從哪裡開始重放。
該位置信息對應於show slave status\G中的Relay_Master_Log_File和Exec_Master_Log_Pos。
# cat relay-log.info
7 ./mysqld-relay-bin.000024 283 mysql-bin.000014 120 0 0 1 1
Crash-Safe Replication
slave每次接受binlog或者應用relay-log時,都要修改master.info或relay-log.info的信息並同步到磁盤中,這會導致大量的磁盤操作,所以,一般都是采用異步方式來對這兩個文件進行更改。雖然每次都會修改這些文件,但是持久化到磁盤卻留給操作系統處理。在內存與磁盤中的信息不一致時,如果此時操作系統宕機,內存中的信息將無法及時同步到磁盤中。操作系統恢復後,master.info和relay-log.info的數據依然是舊的,所以會從已經執行的部分重新執行。
MySQL 5.6為了改善這個問題,提供了兩個參數,可將master.info和relay-log.info中的內容保存在表中而不是磁盤文件中。
master-info-repository
可設置為TABLE或FILE,如果使用FILE,則復制的位置信息依舊保存在master.info中,如果設置為TABLE,則信息報保存在mysql.slave_master_info中,默認為FILE。
relay-log-info-repository
可設置為TABLE或FILE,如果使用FILE,則應用relay-log的信息會保存在relay-log.info中,如果設置為TABLE,則保存在mysql.slave_relay_log_info中,默認為FILE。
通過將上述兩個變量設置為TABLE,可實現“Crash-Safe Replication”,上述的相關操作都會放到一個事務中進行。
總結
1. 啟用復制功能並不會增加服務器太多的開銷,主要是開啟binlog帶來的開銷,包括binlog文件的追加寫操作開銷,以及系統調用fsync帶來的開銷。
2. MySQL的復制功能具有向後兼容性,因為較新版本的MySQL的binlog中會進入新的事件類型。所以,可以將較新版本的MySQL作為從庫。
3. MySQL復制是異步的。
4. MySQL 5.6開始新增延遲復制功能,由master_delay參數來控制。
參考
1. http://dev.mysql.com/doc/refman/5.7/en/replication-configuration.html
2. MariaDB原理與實現