MySQL主备复制原理、实现及异常处理
歡迎支持筆者新作:《深入理解Kafka:核心設(shè)計(jì)與實(shí)踐原理》和《RabbitMQ實(shí)戰(zhàn)指南》,同時(shí)歡迎關(guān)注筆者的微信公眾號(hào):朱小廝的博客。
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/backend/mysql-replication-principle/
復(fù)制概述
MySQL支持三種復(fù)制方式:基于行(Row)的復(fù)制、基于語(yǔ)句(Statement)的復(fù)制和混合類(lèi)型(Mixed)的復(fù)制。
基于語(yǔ)句的復(fù)制早在3.23版本中就存在,而基于行的復(fù)制方式在5.1版本中才被加進(jìn)來(lái)。這兩種方式都是通過(guò)在主庫(kù)上記錄二進(jìn)制日志、在備庫(kù)重放日志的方式來(lái)實(shí)現(xiàn)異步的數(shù)據(jù)復(fù)制。
混合類(lèi)型的復(fù)制:默認(rèn)采用基于語(yǔ)句的復(fù)制,一旦發(fā)現(xiàn)基于語(yǔ)句的無(wú)法精確的復(fù)制時(shí),就會(huì)采用基于行的復(fù)制。
復(fù)制通常不會(huì)增加主庫(kù)的開(kāi)銷(xiāo),主要是啟用二進(jìn)制日志帶來(lái)的開(kāi)銷(xiāo),但出于備份或及時(shí)從崩潰中恢復(fù)的目的,這點(diǎn)開(kāi)銷(xiāo)也是必要的。除此之外,每個(gè)備庫(kù)也會(huì)對(duì)主庫(kù)增加一些負(fù)載(例如網(wǎng)絡(luò)I/O開(kāi)銷(xiāo)),尤其當(dāng)備庫(kù)請(qǐng)求從主庫(kù)讀取舊的二進(jìn)制日志文件時(shí),可能會(huì)造成更高的I/O開(kāi)銷(xiāo)。另外鎖競(jìng)爭(zhēng)也可能阻礙事務(wù)的提交。最后,如果是從一個(gè)高吞吐量的主庫(kù)上復(fù)制到多個(gè)備庫(kù),喚醒多個(gè)復(fù)制線程發(fā)送事件的開(kāi)銷(xiāo)將會(huì)累加。
工作原理
mysql主備復(fù)制實(shí)現(xiàn)分成三個(gè)步驟:
以上只是概述,實(shí)際上每一步都很復(fù)雜:
- 第一步是在主庫(kù)上記錄二進(jìn)制日志。在每次準(zhǔn)備提交事務(wù)完成數(shù)據(jù)更新前,主庫(kù)將數(shù)據(jù)更新的事件記錄到二進(jìn)制日志中。MySQL會(huì)按事務(wù)提交的順序而非每條語(yǔ)句的執(zhí)行順序來(lái)記錄二進(jìn)制日志。在記錄二進(jìn)制日志后,主庫(kù)會(huì)告訴存儲(chǔ)引擎可以提交事務(wù)了。
- 下一步,備庫(kù)將主庫(kù)的二進(jìn)制日志復(fù)制到其本地的中繼日志中。首先,備庫(kù)會(huì)啟動(dòng)一個(gè)工作線程。稱(chēng)為I/O線程,I/O線程跟主庫(kù)建立一個(gè)普通的客戶(hù)端連接,然后在主庫(kù)上啟動(dòng)一個(gè)特殊的二進(jìn)制轉(zhuǎn)儲(chǔ)(binlog dump)線程,這個(gè)二進(jìn)制轉(zhuǎn)儲(chǔ)線程會(huì)讀取主庫(kù)上二進(jìn)制日志中的事件。它不會(huì)對(duì)事件進(jìn)行輪詢(xún)。如果該線程追趕上了主庫(kù),它將進(jìn)入睡眠狀態(tài),直到主庫(kù)發(fā)送信號(hào)量通知其有新的事件產(chǎn)生時(shí)才會(huì)被喚醒,備庫(kù)I/O線程會(huì)將接收到的事件記錄到中繼日志中。
- 備庫(kù)的SQL線程執(zhí)行最后一步,該線程從中繼日志中讀取事件并在備庫(kù)執(zhí)行,從而實(shí)現(xiàn)備庫(kù)數(shù)據(jù)的更新。當(dāng)SQL線程趕上I/O線程時(shí),中繼日志通常已經(jīng)在系統(tǒng)緩存中,所以中繼日志的開(kāi)銷(xiāo)很低。SQL線程執(zhí)行的事件也可以通過(guò)配置選項(xiàng)來(lái)決定是否寫(xiě)入其自己的二進(jìn)制日志中,它對(duì)我們稍后提到的場(chǎng)景非常有用。
主備配置
這里采用的mysql的版本號(hào)是5.5.51,安裝配置可以參考《 Linux(CentOS)中常用軟件安裝,使用及異常——MySQL, VmTools》。
權(quán)限配置
mysql>GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'root@'%' IDENTIFIED BY 'root';復(fù)制賬戶(hù)事實(shí)上只需要有主庫(kù)上的REPLICATION SLAVE權(quán)限,并不一定需要每一端服務(wù)器都有REPLICATION CLIENT權(quán)限,那么為什么我們要把這兩種權(quán)限給主/備庫(kù)都賦予呢?這有兩個(gè)原因:
如果無(wú)腦式配置可以:
主備庫(kù)配置
關(guān)停Master服務(wù)器,將Master中的數(shù)據(jù)拷貝到B服務(wù)器中,使得Master和slave中的數(shù)據(jù)同步,并且確保在全部設(shè)置操作結(jié)束前,禁止在Master和slave服務(wù)器中進(jìn)行寫(xiě)操作,使得兩數(shù)據(jù)庫(kù)中的數(shù)據(jù)一定要相同!
備注:文中采用的案例中主備庫(kù)都有5個(gè)schema:
主庫(kù)的/etc/my.cnf配置(主機(jī)host:xx.xx.xx.73)
[mysqld] log-bin=mysql-bin server-id=1備庫(kù)上也需要在/ect/my.cnf進(jìn)行配置(備機(jī)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沒(méi)有必要開(kāi)啟二進(jìn)制日志,但是在一些情況下,必須設(shè)置,例如,如果slave為其它slave的master,必須設(shè)置 bin_log。在這里,我們開(kāi)啟了二進(jìn)制日志,而且顯示的命名(默認(rèn)名稱(chēng)為hostname,但是,如果hostname改變則會(huì)出現(xiàn)問(wèn)題)。
relay_log配置中繼日志,log_slave_updates表示slave將復(fù)制事件寫(xiě)進(jìn)自己的二進(jìn)制日志(后面會(huì)看到它的用處)。
有 些人開(kāi)啟了slave的二進(jìn)制日志,卻沒(méi)有設(shè)置log_slave_updates,然后查看slave的數(shù)據(jù)是否改變,這是一種錯(cuò)誤的配置。所以,盡量 使用read_only,它防止改變數(shù)據(jù)(除了特殊的線程)。但是,read_only并是很實(shí)用,特別是那些需要在slave上創(chuàng)建表的應(yīng)用。
啟動(dòng)slave
接 下來(lái)就是讓slave連接master,并開(kāi)始重做master二進(jìn)制日志中的事件。你不應(yīng)該用配置文件進(jìn)行該操作,而應(yīng)該使用CHANGE MASTER TO語(yǔ)句,該語(yǔ)句可以完全取代對(duì)配置文件的修改,而且它可以為slave指定不同的master,而不需要停止服務(wù)器。如下:
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,因?yàn)樗侨罩镜拈_(kāi)始位置。
你可以用SHOW SLAVE STATUS語(yǔ)句查看slave的設(shè)置是否正確:
Slave_IO_State, Slave_IO_Running, 和Slave_SQL_Running是No表明slave還沒(méi)有開(kāi)始復(fù)制過(guò)程。日志的位置為4而不是0,這是因?yàn)?只是日志文件的開(kāi)始位置,并不是日志位置。實(shí)際上,MySQL知道的第一個(gè)事件的位置是4。
為了開(kāi)始復(fù)制,你可以運(yùn)行:
mysql> start slave;運(yùn)行show slave status查看輸出結(jié)果:
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線程都已經(jīng)開(kāi)始運(yùn)行,而且Seconds_Behind_Master不再是NULL。日志的位置增加了,意味著一些事件被獲取并執(zhí)行了。如果你在master上進(jìn)行修改,你可以在slave上看到各種日志文件的位置的變化,同樣,你也可以看到數(shù)據(jù)庫(kù)中數(shù)據(jù)的變化。
(如果此時(shí)Slave_SQL_Running=No,可以參考下一節(jié)“異常情況處理”進(jìn)行解決)
你可查看master和slave上線程的狀態(tài)。在master上,你可以看到slave的I/O線程創(chuàng)建的連接(Binlog Dump):
在master上輸入show processlist\G;
同樣,在備庫(kù)也可以看到兩個(gè)線程,一個(gè)是I/O線程,一個(gè)是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異常情況處理
在上一小節(jié)中在start slave之后進(jìn)行show slave status就出現(xiàn)了想要的結(jié)果——“Slave_SQL_Running=Yes”.但是有些時(shí)候,卻不是這樣的:
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上進(jìn)行了寫(xiě)操作,也可能是slave機(jī)器重啟后事務(wù)回滾造成的。
如果是事務(wù)回滾造成的,可以:
最后通過(guò)show slave status進(jìn)行查看。
解決方案2
首先停掉slave服務(wù):
到master上查看主機(jī)狀態(tài):
mysql> show master status; +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000004 | 2395 | | | +------------------+----------+--------------+------------------+ 1 row in set (0.00 sec)然后到slave服務(wù)器上執(zhí)行手動(dòng)同步:
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;案例測(cè)試
在master上的Schema Name: canal_test中有一個(gè)perosn的表,表結(jié)構(gòu)如下:
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 | +----+------+------+------+(注意此時(shí)slave中的數(shù)據(jù)是一樣的)
往master上插入一條數(shù)據(jù),之后查看:
可以看到master中成功插入了一條數(shù)據(jù),之后可以同樣在slave中輸入select * from person來(lái)查看,如果結(jié)果master和slave相同,那么恭喜你主備復(fù)制已經(jīng)成功了。
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/backend/mysql-replication-principle/
參考資料
歡迎支持筆者新作:《深入理解Kafka:核心設(shè)計(jì)與實(shí)踐原理》和《RabbitMQ實(shí)戰(zhàn)指南》,同時(shí)歡迎關(guān)注筆者的微信公眾號(hào):朱小廝的博客。
總結(jié)
以上是生活随笔為你收集整理的MySQL主备复制原理、实现及异常处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 谈谈对Canal(增量数据订阅与消费)的
- 下一篇: 数据库相关中间件收录集