MySQL主备复制原理、实现及异常处理
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
歡迎跳轉到本文的原文鏈接:https://honeypps.com/backend/mysql-replication-principle/
復制概述
MySQL支持三種復制方式:基于行(Row)的復制、基于語句(Statement)的復制和混合類型(Mixed)的復制。
基于語句的復制早在3.23版本中就存在,而基于行的復制方式在5.1版本中才被加進來。這兩種方式都是通過在主庫上記錄二進制日志、在備庫重放日志的方式來實現異步的數據復制。
混合類型的復制:默認采用基于語句的復制,一旦發現基于語句的無法精確的復制時,就會采用基于行的復制。
復制通常不會增加主庫的開銷,主要是啟用二進制日志帶來的開銷,但出于備份或及時從崩潰中恢復的目的,這點開銷也是必要的。除此之外,每個備庫也會對主庫增加一些負載(例如網絡I/O開銷),尤其當備庫請求從主庫讀取舊的二進制日志文件時,可能會造成更高的I/O開銷。另外鎖競爭也可能阻礙事務的提交。最后,如果是從一個高吞吐量的主庫上復制到多個備庫,喚醒多個復制線程發送事件的開銷將會累加。
工作原理
mysql主備復制實現分成三個步驟:
以上只是概述,實際上每一步都很復雜:
- 第一步是在主庫上記錄二進制日志。在每次準備提交事務完成數據更新前,主庫將數據更新的事件記錄到二進制日志中。MySQL會按事務提交的順序而非每條語句的執行順序來記錄二進制日志。在記錄二進制日志后,主庫會告訴存儲引擎可以提交事務了。
- 下一步,備庫將主庫的二進制日志復制到其本地的中繼日志中。首先,備庫會啟動一個工作線程。稱為I/O線程,I/O線程跟主庫建立一個普通的客戶端連接,然后在主庫上啟動一個特殊的二進制轉儲(binlog dump)線程,這個二進制轉儲線程會讀取主庫上二進制日志中的事件。它不會對事件進行輪詢。如果該線程追趕上了主庫,它將進入睡眠狀態,直到主庫發送信號量通知其有新的事件產生時才會被喚醒,備庫I/O線程會將接收到的事件記錄到中繼日志中。
- 備庫的SQL線程執行最后一步,該線程從中繼日志中讀取事件并在備庫執行,從而實現備庫數據的更新。當SQL線程趕上I/O線程時,中繼日志通常已經在系統緩存中,所以中繼日志的開銷很低。SQL線程執行的事件也可以通過配置選項來決定是否寫入其自己的二進制日志中,它對我們稍后提到的場景非常有用。
主備配置
這里采用的mysql的版本號是5.5.51,安裝配置可以參考《 Linux(CentOS)中常用軟件安裝,使用及異常——MySQL, VmTools》。
權限配置
mysql>GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'root@'%' IDENTIFIED BY 'root';復制賬戶事實上只需要有主庫上的REPLICATION SLAVE權限,并不一定需要每一端服務器都有REPLICATION CLIENT權限,那么為什么我們要把這兩種權限給主/備庫都賦予呢?這有兩個原因:
如果無腦式配置可以:
主備庫配置
關停Master服務器,將Master中的數據拷貝到B服務器中,使得Master和slave中的數據同步,并且確保在全部設置操作結束前,禁止在Master和slave服務器中進行寫操作,使得兩數據庫中的數據一定要相同!
備注:文中采用的案例中主備庫都有5個schema:
主庫的/etc/my.cnf配置(主機host:xx.xx.xx.73)
[mysqld] log-bin=mysql-bin server-id=1備庫上也需要在/ect/my.cnf進行配置(備機host:xx.xx.xx.60)
[mysqld] log-bin=mysql-bin server-id=2 relay_log=mysql-relay-bin log_slave_updates=1 read_only=1server_id 是必須的,而且唯一。slave沒有必要開啟二進制日志,但是在一些情況下,必須設置,例如,如果slave為其它slave的master,必須設置 bin_log。在這里,我們開啟了二進制日志,而且顯示的命名(默認名稱為hostname,但是,如果hostname改變則會出現問題)。
relay_log配置中繼日志,log_slave_updates表示slave將復制事件寫進自己的二進制日志(后面會看到它的用處)。
有 些人開啟了slave的二進制日志,卻沒有設置log_slave_updates,然后查看slave的數據是否改變,這是一種錯誤的配置。所以,盡量 使用read_only,它防止改變數據(除了特殊的線程)。但是,read_only并是很實用,特別是那些需要在slave上創建表的應用。
啟動slave
接 下來就是讓slave連接master,并開始重做master二進制日志中的事件。你不應該用配置文件進行該操作,而應該使用CHANGE MASTER TO語句,該語句可以完全取代對配置文件的修改,而且它可以為slave指定不同的master,而不需要停止服務器。如下:
mysql> CHANGE MASTER TO -> MASTER_HOST='xx.xx.xx.73',-> MASTER_USER='root',-> MASTER_PASSWORD='xxxx',-> MASTER_LOG_FILE='mysql-bin.000004',-> MASTER_LOG_POS=0;MASTER_LOG_POS的值為0,因為它是日志的開始位置。
你可以用SHOW SLAVE STATUS語句查看slave的設置是否正確:
Slave_IO_State, Slave_IO_Running, 和Slave_SQL_Running是No表明slave還沒有開始復制過程。日志的位置為4而不是0,這是因為0只是日志文件的開始位置,并不是日志位置。實際上,MySQL知道的第一個事件的位置是4。
為了開始復制,你可以運行:
mysql> start slave;運行show slave status查看輸出結果:
mysql> show slave status\G *************************** 1. row ***************************Slave_IO_State: Waiting for master to send eventMaster_Host: xx.xx.xx.73Master_User: rootMaster_Port: 3306Connect_Retry: 60Master_Log_File: mysql-bin.000004Read_Master_Log_Pos: 2395Relay_Log_File: mysql-relay-bin.000002Relay_Log_Pos: 253Relay_Master_Log_File: mysql-bin.000004Slave_IO_Running: YesSlave_SQL_Running: YesReplicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0Last_Error: Skip_Counter: 0Exec_Master_Log_Pos: 2395Relay_Log_Space: 409Until_Condition: NoneUntil_Log_File: Until_Log_Pos: 0Master_SSL_Allowed: NoMaster_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: NoLast_IO_Errno: 0Last_IO_Error: Last_SQL_Errno: 0Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1在這里主要是看:
Slave_IO_Running=YesSlave_SQL_Running=Yesslave的I/O和SQL線程都已經開始運行,而且Seconds_Behind_Master不再是NULL。日志的位置增加了,意味著一些事件被獲取并執行了。如果你在master上進行修改,你可以在slave上看到各種日志文件的位置的變化,同樣,你也可以看到數據庫中數據的變化。
(如果此時Slave_SQL_Running=No,可以參考下一節“異常情況處理”進行解決)
你可查看master和slave上線程的狀態。在master上,你可以看到slave的I/O線程創建的連接(Binlog Dump):
在master上輸入show processlist\G;
同樣,在備庫也可以看到兩個線程,一個是I/O線程,一個是SQL線程(Connect):
mysql> show processlist\G *************************** 1. row ***************************Id: 3User: rootHost: xx.xx.xx.60:62159db: NULL Command: Binlog DumpTime: 67811State: Master has sent all binlog to slave; waiting for binlog to be updatedInfo: NULL *************************** 2. row ***************************Id: 14User: rootHost: localhostdb: canal_test Command: QueryTime: 0State: NULLInfo: show processlist *************************** 3. row ***************************Id: 19User: rootHost: xx.xx.xx.60:62390db: NULL Command: SleepTime: 187State: Info: NULL *************************** 4. row ***************************Id: 20User: system userHost: db: NULL Command: ConnectTime: 64State: Waiting for master to send eventInfo: NULL *************************** 5. row ***************************Id: 21User: system userHost: db: NULL Command: ConnectTime: 64State: Slave has read all relay log; waiting for the slave I/O thread to update itInfo: NULL異常情況處理
在上一小節中在start slave之后進行show slave status就出現了想要的結果——“Slave_SQL_Running=Yes”.但是有些時候,卻不是這樣的:
mysql> show slave status\G *************************** 1. row ***************************Slave_IO_State: Waiting for master to send eventMaster_Host: xx.xx.xx.73Master_User: rootMaster_Port: 3306Connect_Retry: 60Master_Log_File: mysql-bin.000004Read_Master_Log_Pos: 2172Relay_Log_File: mysql-relay-bin.000002Relay_Log_Pos: 253Relay_Master_Log_File: mysql-bin.000004Slave_IO_Running: YesSlave_SQL_Running: NoReplicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 1007Last_Error: Error 'Can't create database 'canal_test'; database exists' on query. Default database: 'canal_test'. Query: 'create database canal_test'Skip_Counter: 0Exec_Master_Log_Pos: 107Relay_Log_Space: 2474Until_Condition: NoneUntil_Log_File: Until_Log_Pos: 0Master_SSL_Allowed: NoMaster_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: NULL Master_SSL_Verify_Server_Cert: NoLast_IO_Errno: 0Last_IO_Error: Last_SQL_Errno: 1007Last_SQL_Error: Error 'Can't create database 'canal_test'; database exists' on query. Default database: 'canal_test'. Query: 'create database canal_test'Replicate_Ignore_Server_Ids: Master_Server_Id: 1可以看到Slave_SQL_Running=No,那么該怎么解決呢?
解決方案1
程序可能在slave上進行了寫操作,也可能是slave機器重啟后事務回滾造成的。
如果是事務回滾造成的,可以:
最后通過show slave status進行查看。
解決方案2
首先停掉slave服務:
到master上查看主機狀態:
mysql> show master status; +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000004 | 2395 | | | +------------------+----------+--------------+------------------+ 1 row in set (0.00 sec)然后到slave服務器上執行手動同步:
mysql> change master to -> master_host='xx.xx.xx.73',-> master_user='root',-> master_password='xxxx',-> master_port=3306,-> master_log_file='mysql-bin.000004',-> master_log_pos=2395; mysql> slave start;案例測試
在master上的Schema Name: canal_test中有一個perosn的表,表結構如下:
mysql> describe person; +-------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+--------------+------+-----+---------+-------+ | id | int(11) | NO | PRI | NULL | | | name | varchar(100) | YES | | NULL | | | age | int(11) | YES | | NULL | | | sex | char(1) | YES | | NULL | | +-------+--------------+------+-----+---------+-------+表中有一條記錄:
mysql> select * from person; +----+------+------+------+ | id | name | age | sex | +----+------+------+------+ | 2 | zzh2 | 21 | m | +----+------+------+------+(注意此時slave中的數據是一樣的)
往master上插入一條數據,之后查看:
可以看到master中成功插入了一條數據,之后可以同樣在slave中輸入select * from person來查看,如果結果master和slave相同,那么恭喜你主備復制已經成功了。
歡迎跳轉到本文的原文鏈接:https://honeypps.com/backend/mysql-replication-principle/
參考資料
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
總結
以上是生活随笔為你收集整理的MySQL主备复制原理、实现及异常处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谈谈对Canal(增量数据订阅与消费)的
- 下一篇: 数据库相关中间件收录集