MySQL主从复制原理学习
一、主從復(fù)制過(guò)程
master 服務(wù)器將數(shù)據(jù)的改變記錄二進(jìn)制 binlog 日志,當(dāng) master 上的數(shù)據(jù)發(fā)生改變時(shí),則將其改變寫(xiě)入二進(jìn)制日志中;
slave 服務(wù)器會(huì)在一定時(shí)間間隔內(nèi)對(duì) master 二進(jìn)制日志進(jìn)行探測(cè)其是否發(fā)生改變,如果發(fā)生改變,則開(kāi)始一個(gè) I/OThread 請(qǐng)求 master 二進(jìn)制事件;
同時(shí)主節(jié)點(diǎn)為每個(gè) I/O 線程啟動(dòng)一個(gè) dump 線程,用于向其發(fā)送二進(jìn)制事件,并保存至從節(jié)點(diǎn)本地的中繼日志中,從節(jié)點(diǎn)將啟動(dòng) SQL 線程從中繼日志中讀取二進(jìn)制日志,在本地重放,使得其數(shù)據(jù)和主節(jié)點(diǎn)的保持一致,最后 I/OThread 和 SQLThread 將進(jìn)入睡眠狀態(tài),等待下一次被喚醒。
1.1 binlog
主從復(fù)制是在binlog的基礎(chǔ)上進(jìn)行的
binlog格式可以設(shè)置為:statement、row、mixed三種
- Statement 基于語(yǔ)句,只記錄對(duì)數(shù)據(jù)做了修改的SQL語(yǔ)句,能夠有效的減少binlog的數(shù)據(jù)量,提高讀取、基于binlog重放的性能
- Row 只記錄被修改的行,所以Row記錄的binlog日志量一般來(lái)說(shuō)會(huì)比Statement格式要多?;赗ow的binlog日志非常完整、清晰,記錄了所有數(shù)據(jù)的變動(dòng),但是缺點(diǎn)是可能會(huì)非常多,例如一條update語(yǔ)句,有可能是所有的數(shù)據(jù)都有修改;再例如alter table之類的,修改了某個(gè)字段,同樣的每條記錄都有改動(dòng)。
- Mixed Statement和Row的結(jié)合,怎么個(gè)結(jié)合法呢。例如像update或者alter table之類的語(yǔ)句修改,采用Statement格式。其余的對(duì)數(shù)據(jù)的修改例如update和delete采用Row格式進(jìn)行記錄。
1.2 relaylog
二、異步復(fù)制、半同步復(fù)制
2.1 slave io_thread
1.根據(jù)master的ip和port,slave_io線程連接到master,如果連接不上,會(huì)發(fā)起重試
間隔時(shí)間:master_connect_retry(默認(rèn) 60s)
2.發(fā)送sql命令 設(shè)置主庫(kù)master_heartbeat_period(默認(rèn)slave_net_timeout/2)以及計(jì)算主從時(shí)間差 clock_diff_with_master(主從機(jī)器的系統(tǒng)時(shí)間差+網(wǎng)絡(luò)延遲)
3.io線程發(fā)送com_binlog_dump協(xié)議請(qǐng)求
4.waiting for master to send event
5.queueing master event to the relay log喚醒sql線程
6.修改master log pos的值 flush master_info(FLUSH頻率為sync_master_info的值)將主庫(kù)信息寫(xiě)入master_log_info表
7.繼續(xù)步驟3 循環(huán)
2.2 主庫(kù)binlog_dump流程
1.DUMP線程收到COM BINLOG DUMP報(bào)文,解析上送filename和pos,并設(shè)置心跳包間隔時(shí)間master heartbeat period以及發(fā)送ROTATE EVENT更新從庫(kù)的masterlog_name
2.進(jìn)入"Sending binlog event to slave"狀態(tài),準(zhǔn)備發(fā)送event到SlavelO線程
3.獲取讀取的binlog的尾指針位置,如果已達(dá)到hot binlog即最新的binlog文件)的尾部,則處于"Master has sent all binlog to slave; waiting for more updates"等待主庫(kù)產(chǎn)生binlog
4.獲取到尾指針,將相關(guān)的event發(fā)送到SlavelO一個(gè)文件結(jié)束
5.繼續(xù)下個(gè)文件執(zhí)行步驟3,如此循環(huán) 純真笑容
2.3 slave_sql線程流程
1.對(duì)SlaveSQL線程進(jìn)行參數(shù)初始化,并設(shè)置需要開(kāi)始讀取relaylog的name和 pos.
2.進(jìn)入"Reading event from the relay log"狀態(tài),開(kāi)始讀取event
3.如果沒(méi)有新event,進(jìn)入"Slave has read all relay loq: waiting for more updates"狀態(tài),等待Slave_IO線程寫(xiě)入新event
4.讀取到event設(shè)置lastmastertimestamp以及sql線程的starttime計(jì)算主從延時(shí)的重要依據(jù))并判斷是否需要跳過(guò)event的apply
5.根據(jù)event的數(shù)據(jù)在從庫(kù)進(jìn)行apply應(yīng)用處理
6.對(duì)于非xid的event,更新讀取relaylog的文件指針即event relaylog pos值當(dāng)一個(gè)事務(wù)結(jié)束時(shí)候,會(huì)flushinfo(true)將rli信息強(qiáng)制寫(xiě)入relayloginfo表(默認(rèn) relay_log_info_repository=TABLE)
7.再次讀取下個(gè)event進(jìn)入步驟2如此循環(huán) 純真笑容
2.4 半同步復(fù)制
2.4.1 sync binlog
-
當(dāng)sync_binlog為0的時(shí)候,binlog sync磁盤(pán)由OS負(fù)責(zé).
-
當(dāng)sync_binlog值大于1的時(shí)候,sync binlog操作可能并沒(méi)有使binlog落盤(pán)(需要達(dá)到sync_binlog值之后才會(huì)進(jìn)行fsync).如果沒(méi)有實(shí)際落盤(pán),事務(wù)在提交前,Master crash了,Master再次啟動(dòng)后原事務(wù)就會(huì)被回滾。但可能Dump線程已將events同步到Slave,并且Slave已經(jīng)應(yīng)用了這些events,這也會(huì)導(dǎo)致Slave數(shù)據(jù)比Master多,主備同步失敗。
2.4.2 sync relay log
- 參數(shù)解釋
當(dāng)sync_relay_log為0的時(shí)候,relaylog sync磁盤(pán)由OS負(fù)責(zé).
當(dāng)sync_relay_log>1的時(shí)候,semisync返回給Master的position可能沒(méi)有fsync到磁盤(pán).
sync_relay_log=1的時(shí)候,會(huì)保證semisync返回給Master的positiony已經(jīng)fsync到磁盤(pán).
- 異常舉例:
在gtid_mode下,在sync_binlog=1的情況下,sync_relay_log不是1的時(shí)候,僅發(fā)生Master或Slave的一次Crash并不會(huì)發(fā)生數(shù)據(jù)丟失或者主備同步失敗情況。
如果發(fā)生Slave沒(méi)有sync relay log,Master端事務(wù)提交,然后Slave端Crash。這樣Slave端就會(huì)丟失掉已經(jīng)回復(fù)Master ACK的事務(wù)events。
但當(dāng)Slave再次啟動(dòng),如果沒(méi)有來(lái)得及從Master端同步丟失的事務(wù)Events,Master就Crash。這個(gè)時(shí)候,用戶訪問(wèn)Slave就會(huì)發(fā)現(xiàn)數(shù)據(jù)丟失.
如果完全要保證半同步的主從數(shù)據(jù)的一致性需要設(shè)置sync_binlog和sync_relay_log都為1,但是這會(huì)帶來(lái)性能的瓶頸(尤其是sync_relay_log為1 每同步一個(gè)event就需要fsync).所以實(shí)際的應(yīng)用中,會(huì)根據(jù)數(shù)據(jù)一致性和性能的綜合考慮設(shè)置合理的值.
三、常見(jiàn)主從報(bào)錯(cuò)與解決方式
3.1 Last error主從數(shù)據(jù)不一致
查看導(dǎo)致last error產(chǎn)生的信息
#方法1——通過(guò)binlog查詢 show binlog events in 'mysql-bin.032102' from 730019106 limit 10; #找到對(duì)應(yīng)行,該行中Info信息就是1973位置所做操作 #方法2——通過(guò)ps表查詢 select * from performance_schema.replication_applier_status_by_worker where LAST_ERROR_NUMBER=1396如果確認(rèn)可以跳過(guò)或者短時(shí)間無(wú)法解決
# 跳過(guò)指定數(shù)量的事務(wù),通常為1跳過(guò)當(dāng)前出錯(cuò)事務(wù) mysql > stop slave ; mysql > set global sql_slave_skip_counter=1 mysql > start slave ;# 跳過(guò)指定類型的錯(cuò)誤或者所有錯(cuò)誤 vi /etc/my.cnf [mysqld] slave-skip-errors=1062,1146,2341 #跳過(guò)指定錯(cuò)誤類型 slave-skip-errors=all #跳過(guò)所有錯(cuò)誤,不建議使用# GTID模式下跳過(guò)事務(wù) stop slave; set gtid_next='fb6d07d2-a253-1212-b2fh-29255eg3f3g:23' #show slave status信息中retrieved_gtid_set里的gtid為 fb6d07d2-a253-1212-b2fh-29255eg3f3g:18-23 begin;commit; #手動(dòng)指定gtid_next,如果gtid已經(jīng)存在于實(shí)例的GTID集合中,該事務(wù)會(huì)被忽略;如果沒(méi)有存在于GTID集合中就將這個(gè)gtid分配給接下來(lái)要執(zhí)行的事務(wù),系統(tǒng)不需要給這個(gè)事務(wù)生成新的GTID set gtid_next='AUTOMATIC'; #修改回自動(dòng)獲取GTID start slave;# 使用gtid_purged跳過(guò)事務(wù) show slave status \G; #先查看executed_gtid_set的值,該值有兩行,看下面那行 show variables like '%gtid_purged%' #查看主庫(kù)purged值,假設(shè)末尾是1-33 reset master #清空從庫(kù)binlog和gtid_executed狀態(tài) set global gtid_purged='xxxxxxxxxxxxxxxxxxxxxxxxx:1-33'; 跳過(guò)1-33的事務(wù) start slave; #主從狀態(tài)恢復(fù)后需手動(dòng)補(bǔ)跳過(guò)的事務(wù)所產(chǎn)生的數(shù)據(jù)3.2 連接主庫(kù)報(bào)錯(cuò)
Last_IO_Errno: 1040 Last_IO_Error: error reconnecting to master 'repl@10.0.0.51:3307' - retry-time: 10 retries: 7通過(guò)復(fù)制用戶 手動(dòng)連接
判斷是否是主庫(kù)連接數(shù)過(guò)多還是防火墻不同
3.3 relay log損壞
Last_SQL_Error: Error initializing relay log position: I/O error reading the header from the binary log Last_SQL_Error: Error initializing relay log position: Binlog has bad magic number; It's not a binary log file that can be used by this version of MySQL找到同步的binlog和POS點(diǎn),然后重新做同步,這樣就可以有新的relaylog
Relay_Master_Log_File: mysql-bin.000010 Exec_Master_Log_Pos: 821 mysql> stop slave; Query OK, 0 rows affected (0.01 sec) mysql> CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000010',MASTER_LOG_POS=821; Query OK, 0 rows affected (0.01 sec) mysql> start slave;3.4 主庫(kù)crash 從庫(kù)重復(fù)回放
在開(kāi)啟 GTID 模式下,如果指定 master_auto_position=1,start slave 時(shí),從庫(kù)會(huì)把 Retrieved_Gtid_Set 和 Executed_Gtid_Set 的并集發(fā)送給主庫(kù),主庫(kù)將收到的并集和自己的 gtid_executed 比較,把從庫(kù) GTID 集合里缺失的事務(wù)全都發(fā)送給從庫(kù)。
主機(jī)重啟后,事務(wù)重復(fù)回放,表明 Retrieved_Gtid_Set 和 Executed_Gtid_Set 的并集中有 GTID 事務(wù)丟失,導(dǎo)致重復(fù)獲取事務(wù)執(zhí)行引發(fā)主鍵沖突錯(cuò)誤。
Retrieved_Gtid_Set 和 Executed_Gtid_Set 均為內(nèi)存變量,MySQL 重啟后,Retrieved_Gtid_Set 初始化為空值,從而推斷出 Executed_Gtid_Set 有 GTID 事務(wù)丟失。
Executed_Gtid_Set 來(lái)源于 gtid_executed 變量,gtid_executed 變量持久化介質(zhì)有 mysql.gtid_executed 表和 binlog 日志
當(dāng) log_bin=on ,log_slave_updates=on 時(shí),只有在 binlog 切換時(shí)侯才會(huì)更新 mysql.gtid_executed 表,保存直到上一個(gè) binlog 執(zhí)行過(guò)的 GTID 集合。MySQL 重啟后,在默認(rèn)參數(shù) binlog_gtid_simple_recovery=1 時(shí),gtid_executed 變量值從最后一個(gè) binlog 文件計(jì)算獲得
Worker 線程報(bào) 1062 主鍵沖突錯(cuò)誤–> gtid_executed 信息陳舊–> binlog 未實(shí)時(shí)持久化
3.5 級(jí)聯(lián)復(fù)制報(bào)錯(cuò)
針對(duì)雙主級(jí)聯(lián)復(fù)制,需要檢查從庫(kù)的read_only選項(xiàng),雙主模式下的自增步長(zhǎng),以及log_slave_updates參數(shù),否則雙主模式下,可能會(huì)有問(wèn)題
3.6 主從切換后gtid不一致
從庫(kù)之前執(zhí)行過(guò)命令導(dǎo)致產(chǎn)生了新的gtid,并且該gtid所在的binlog已經(jīng)被清理
導(dǎo)致發(fā)生主從切換之后,新從庫(kù)同步復(fù)制新主庫(kù)時(shí),由于master_auto_position=1模式下,從庫(kù)會(huì)將當(dāng)前已經(jīng)執(zhí)行過(guò)的gtid集合發(fā)送給主庫(kù),主庫(kù)接收到從庫(kù)發(fā)送的gtid集合后,會(huì)與當(dāng)前已經(jīng)執(zhí)行的gtid set求差值,并將沒(méi)有執(zhí)行過(guò)的gtid發(fā)送給備庫(kù),由于binlog被清理導(dǎo)致無(wú)法將gtid發(fā)送給從庫(kù)(gtid_purged沒(méi)有包含)
解決:在新從庫(kù)手動(dòng)跳過(guò)缺失的gtid(注:跳過(guò)缺失的gtid,意味著不在從庫(kù)執(zhí)行缺失的事務(wù),可能導(dǎo)致數(shù)據(jù)的丟失)
數(shù)據(jù)恢復(fù):利用主庫(kù)備份重做或者利用binlog備份 恢復(fù)到原來(lái)的日志目錄(需要的binlog要從第一個(gè)包含缺失的gtid日志到當(dāng)前的全部日志)
3.7 多線程復(fù)制報(bào)錯(cuò)
對(duì)于多線程復(fù)制,slave_pending_jobs_size_max變量設(shè)置用于保存尚未應(yīng)用的event的工作隊(duì)列可用的最大內(nèi)存量(以字節(jié)為單位)。設(shè)置此變量對(duì)未啟用多線程處理的復(fù)制沒(méi)有影響。設(shè)置此變量不會(huì)立即生效。必須要停掉復(fù)制之后,重新start slave。
此變量的最小值為1024;默認(rèn)值為16MB。最大可能值為18446744073709551615(16 EB)。
此變量的值是軟限制,可以設(shè)置為與正常工作負(fù)載匹配。如果異常大的事件超過(guò)此大小,事務(wù)將被保留,直到所有工作線程都有空隊(duì)列,然后進(jìn)行處理。如果內(nèi)存富余,或者延遲較大時(shí),可以適當(dāng)調(diào)大;注意這個(gè)值要比主庫(kù)的max_allowed_packet大!
當(dāng)worker線程正在處理的event的總大小超過(guò)slave_pending_jobs_size_max變量的大小時(shí),將發(fā)生此等待操作。此時(shí)可有在主庫(kù)看到線程的狀態(tài)為:Waiting for Slave Workers to free pending events
總結(jié)
以上是生活随笔為你收集整理的MySQL主从复制原理学习的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 量化交易入门阶段:布林带调整参数又如何?
- 下一篇: ecshop后台首页mysql_ecsh