前言
一、主從復制過程

MySQL的主從復制能力是通過三個線程來實現的,兩個在Slave端的I/O和SQL兩個線程,還有一個在Master端I/O線程:
- Binlog dump thread:Master端創建該線程來響應Slave端I/O線程的請求,向Slave端發送二進制內容。Binlog dump線程讀取主服務器二進制內容前會對其加鎖,讀取結束后無論內容是否被發送到Slave端,鎖都會被即刻釋放。在一主多從架構中,Master端會為每個連接的I/O線程創建一個Binlog dump線程。
- Slave I/O thread:當Slave端執行START SLAVE命令時,Slave端隨即創建一個I/O線程,並連接Master端請求更新。Slave端的I/O線程從Master端的Binlog dump線程讀取更新並拷貝到本地Relay log(中繼日志)中。
- Slave SQL thread:Slave創建的第二個線程是SQL線程,該線程從Relay log中讀取和執行語句。
復制過程中涉及的三個核心線程介紹完了,我們來總結下完整的復制過程:
1)、Slave創建I/O線程並連接Master,請求日志文件的指定位置(或者從最開始的日志)之后的日志內容。
2)、Master服務器接收到來自Slave服務器的I/O線程請求后,會隨之創建Binlog Dump線程來響應這個線程。然后,Binlog Dump線程根據請求信息讀取指定日志位置之后的日志信息,返回給Slave端的I/O線程。
3)、Slave的I/O線程接收到信息后,將日志內容依次寫入Slave端的Relay Log(中繼日志)文件中,並將讀取到的Master端的二進制文件名和位置記錄到master-info文件中。
4)、Slave的SQL線程檢測到Relay Log中新內容后,會馬上解析並執行這些語句。
我們可以看出Slave服務器上的I/O和SQL兩個線程使得讀取和執行語句被分成兩個獨立的任務。因此,即使某條語句在Slave服務器上執行緩慢,但語句讀取任務並不會慢下來。
MySQL semi-sync replication(半同步復制)在之前的基礎上做了加強完善,整個流程變成了下面這樣:
- 首先,master和至少一個slave都要啟用semi-sync replication模式;
- 某個slave連接到master時,會主動告知當前自己是否處於semi-sync模式;
- 在master上提交事務后,寫入binlog后,還需要通知至少一個slave收到該事務,等待寫入relay log並成功刷新到磁盤后,向master發送“slave節點已完成該事務”確認通知;
- master收到上述通知后,才可以真正完成該事務提交,返回事務成功標記;
- 在上述步驟中,當slave向master發送通知時間超過rpl_semi_sync_master_timeout設定值時,主從關系會從semi-sync模式自動調整成為傳統的異步復制模式。
半同步復制看起來似乎可以異步復制的痛點,但如果網絡質量不高,是不是出現抖動,觸發上述第5條的情況,會從半同步復制降級為異步復制;此外,采用半同步復制,會導致master上的tps性能下降非常嚴重,最嚴重的情況下可能會損失50%以上。

二、數據一致性
我們看到主從復制的過程簡單清晰,但是會存在如下情況影響主從數據的一致性:
- 數據丟失
- 數據重復
1、MySQL主從復制又是怎么解決數據丟失和數據重復,進而保障主從數據的一致性哪?
- 從庫的重傳檢查點
我們知道從庫的 I/O Thread 是通過網絡讀取主庫的 binlog 的,如果出現網絡故障,有可能產生數據丟失。為避免網絡故障導致的數據丟失,網絡恢復后從庫重新連接上來需要知道從主庫 binlog 的哪個位置重新傳輸數據。從庫需要記住中斷發生時 binlog 的位置,並從該斷點處重新讀取,這個斷點我們稱為從庫的重傳檢查點。一個可靠的重傳檢查點必須是在從庫讀到數據並寫入到本地 Relay log 持久化之后才可建立,否則都有丟失數據的可能。
- 防重策略
我們知道分布式特征具有冪等性,也就是重復復制同一條數據最終不會產生重復的數據。
因此需要保證主從復制過程的冪等性,一般符合范式特征的數據庫表設計通過主鍵來防重,而無主鍵表數據可以通過所有字段聯合唯一索引來防重。有了防重策略就可以任意回溯復制過程,而不必考慮從庫產生重復數據。
我們知道binlog 的事件日志反應了主庫並發事務的操作序列,最終這種序列也要原樣反應到從庫上。
MySQL主從復制架構為了保證主從數據一致性,復制過程不僅要保證不丟失、不重復,還需要保證操作順序一致。采用了單線程模型的串行化操作。因為在數據庫層面是無法知道不同數據之間的因果和依賴關系,因此無法並行入庫。
2、MySQL主從復制架構做到了無丟失、無重復和順序一致性,但也存在一些不足:
- 可監控性、可管理性相對較弱。
- 對於異構數據無能為力。
- 通用的單線程模型可能成為性能瓶頸,導致復制延時過高。
- 一對多場景下對主庫形成過大復制壓力,影響主庫可用性。
3、一些特殊場景下的分布式數據庫架構中,使用這種復制架構則不一定合適,場景如:
- 大型庫,數據量大,寫入量大,還需要跨地域、跨機房的復制,而且對復制延時長短比較敏感,比如交易類數據庫、訂單類數據庫。
三、數據復制延遲
數據復制延遲主要原因:主庫多線程更新,從庫單線程更新。
Master產生二進制日志由於buffer和順序IO的關系,效率非常高。Slave的I/O線程到Master端取日志的過程效率也比較高,但Slave的SQL線程在應用Relay Log中的操作時產生IO操作是隨機的而不是順序的,所以時間成本會高非常多,不僅如此,甚至還可能同Slave上的其他查詢產生鎖爭用。由於Slave的SQL線程是單線程的,Relay Log中的語句只能一個個的被執行,所以所有的語句只能等待它之前的語句被執行完才會執行,這就導致了延時。
我們可以通過vmstat之類的命令來進行驗證。觀察”r”和”b”兩列數值,如果r列值很大,說明服務器CPU達到瓶頸,如果b列值很大,說明服務器IO達到瓶頸。
1)、如果復制過程受磁盤IO限制,可以嘗試以下解決方法:
-
- 優化Mysql參數(增大innodb_buffer_pool_size) 和 增加內存,讓更多操作在Mysql內存中完成,減少磁盤操作。
- 使用SSD磁盤,提升IO性能。
- 嘗試對二進制日志內容進行壓縮傳輸
- 業務代碼優化,將實時性要求高的某些操作,使用主庫做讀操作。
2)、如果問題發生在SQL線程
多線程同步:使用淘寶的Transfer解決方案,是一個基於MySQL+patch后得到的主從同步工具。該工具的原理是以多線程的方式來讀取relaylog更新SlAVE的方式。。
3)、如果復制過程受CPU限制,使用高性能CPU主機或者參考Peter Zaitsev寫的Fighting MySQL Replication Lag。
4)、采用MariaDB發行版,它實現了相對真正意義上的並行復制,其效果遠比ORACLE MySQL好的很多。在我的場景中,采用MariaDB作為slave的實例,幾乎總是能及時跟上master。如果不想用這個版本的話,那就老實等待官方5.7大版本發布吧;
關於MariaDB的Parallel Replication具體請參考:Replication and Binary Log Server System Variables#slave_parallel_threads – MariaDB Knowledge Base
5)、每個表都要顯式指定主鍵,如果沒有指定主鍵的話,會導致在row模式下,每次修改都要全表掃描,尤其是大表就非常可怕了,延遲會更嚴重,甚至導致整個slave庫都被掛起,可參考案例:mysql主鍵的缺少導致備庫hang。
6)、應用程序端多做些事,讓MySQL端少做事,尤其是和IO相關的活動,例如:前端通過內存CACHE或者本地寫隊列等,合並多次讀寫為一次,甚至消除一些寫請求。
7)、進行合適的分庫、分表策略,減小單庫單表復制壓力,避免由於單庫單表的的壓力導致整個實例的復制延遲。
四、延遲檢測
1、Seconds_Behind_Master,並不能表示主從數據一致
表示slave上SQL thread與IO thread之間的延遲,我們都知道在MySQL的復制環境中,slave先從master上將binlog拉取到本地(通過IO thread),然后通過SQL thread將binlog重放,而Seconds_Behind_Master表示本地relaylog中未被執行完的那部分的差值。
手冊上的定義:In essence, this field measures the time difference in seconds between the slave SQL thread and the slave I/O thread.
所以如果slave拉取到本地的relaylog都執行完,此時通過show slave status看到的會是0。
- 那么Seconds_Behind_Master的值為0是否表示主從已經處於一致了呢?答案幾乎是否定的!
因為絕大部分的情況下復制都是異步的(MySQL5.5級以后版本中支持了半同步復制),異步就意味着master上的binlog不是實時的發送到slave上,所以即使Seconds_Behind_Master的值為0依然不能肯定主從處於一致。所以我們只能在一個比較好的網絡環境中以這個參數來估計主從延遲。
- Seconds_Behind_Master的值到底是怎么計算出來的呢?
實際上在binlog中每個binlog events都會附上執行時的timestamp,所以在在確定Seconds_Behind_Master的值時MySQL是通過比較當前系統的時間戳與當前SQL thread正在執行的binlog event的上的時間戳做比較,這個差值就是Seconds_Behind_Master的值。也許你會有疑問那要是兩台服務器之間的時鍾不一致怎么辦?確實會存在這種情況,那么此時這個值的可靠性就不大了,手冊上對此也進行了說明:
This time difference computation works even if the master and slave do not have identical clock times, provided that the difference,
computed when the slave I/O thread starts, remains constant from then on. Any changes—including NTP updates—can lead to clock
skews that can make calculation of Seconds_Behind_Master less reliable
Seconds_Behind_Master的值除了是非負數之外還可能是NULL,它是由如下幾種情況導致的:SQL thread沒運行/IO thread沒運行/slave沒有連接到master。
異步復制,master上的操作記錄binlog的同時不關心binlog是否已經被slave接收。
半同步復制,master上的操作記錄binlog的同時會關心binlog是否被slave接受。
2、Master_Log_File/Read_Master_Log_Pos、Relay_Log_File/Relay_Log_Pos、Relay_Master_Log_File/Exec_Master_Log_Pos
1)、The position, ON THE MASTER, from which the I/O thread is reading:Master_Log_File/Read_Master_Log_Pos.
相對於主庫,從庫讀取主庫的二進制日志的位置,是IO線程
2)、The position, IN THE RELAY LOGS, at which the SQL thread is executing:Relay_Log_File/Relay_Log_Pos
相對於從庫,是從庫的sql線程執行到的位置
3)、The position, ON THE MASTER, at which the SQL thread is executing:Relay_Master_Log_File/Exec_Master_Log_Pos
相對於主庫,是從庫的sql線程執行到的位置
2) 和 3) are the same thing, but one is on the slave and the other is on the master.
mysql > show slave status \G
Master_Log_File: mysql-bin-m.000329
Read_Master_Log_Pos: 863952156 ----上面二行代表IO線程,相對於主庫的二進制文件
Relay_Log_File: mysql-relay.003990
Relay_Log_Pos: 25077069 ----上面二行代表了sql線程,相對於從庫的中繼日志文件
Relay_Master_Log_File: mysql-bin-m.000329
.....
Exec_Master_Log_Pos: 863936961---上面二行代表了sql線程,相對主庫
Relay_Log_Space: 25092264---當前relay-log文件的大小
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
從上面可以看到,read_master_log_pos 始終會大於exec_master_log_pos的值(也有可能相等)。因為一個值是代表io線程,一個值代表sql線程;sql線程肯定在io線程之后.(當然,io線程和sql線程要讀寫同一個文件,否則比較就失去意義了) .
3、percona-toolkit工具對mysql主從數據庫的同步狀態進行檢查和重新同步
pt-heartbeat: 監控MySQL從服務器的延時時間。
pt-slave-restart: 管理MySQL從服務器重啟。
pt-table-checksum: 檢查主從同步數據的一致性,比如遇到復制錯誤,我們執行了skip error操作之后,檢查一下數據還是很有必要的。不過這個工具需要提前設置一下,安裝相應的checksum表。
五、主從數據不同步解決
http://ylw6006.blog.51cto.com/470441/1616570
