您好,登錄后才能下訂單哦!
MySQL中主從復制的原理是什么,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
0. 主從復制
首先主從復制是什么?簡單來說是讓一臺MySQL服務器去復制另一臺MySQL的數據,使兩個服務器的數據保持一致。
這種方式與Redis的主從復制的思路沒有太大的出入。如果你對Redis的主從復制感興趣可以去看看《Redis的主從復制》。那既然Redis和MySQL都采用了復制這種方式,主從復制所帶來的意義是什么呢?
通過復制功能,構建一個或者多個從庫,可以提高數據庫的高可用性、可擴展性,同時實現負載均衡。當主庫發生故障時,可以快速的切到其某一個從庫,并將該從庫提升為主庫,因為數據都一樣,所以不會影響系統的運行;當MySQL服務器需要扛住更多的讀請求時,可以把讀請求的流量分流到各個從庫上去,寫請求則轉發給主庫,形成讀寫分離的架構,來提供更好的讀擴展和請求的負載均衡。
讀寫分離的架構應用的其實非常廣泛,就比如MySQL,還有Redis,以及我們熟悉的Zookeeper,Zookeeper的Follower收到讀請求不會自己處理,而是會將讀請求轉發給Leader,感興趣的可以自己下來了解一下,這里就不偏題了。
1. 復制原理
MySQL的主從復制支持兩種方式:
基于行
基于語句
基于語句的復制在MySQL3.23中就已經有了,而基于語句的方式則在5.1中才實現。其本質都是基于主庫的binlog來實現的,主庫記錄binlog,然后從庫將binlog在自己的服務器上重放,從而保證了主、從的數據一致性。
1.1 binlog
MySQL中日志分為兩個維度,一個是MySQL服務器的,一個是底層存儲引擎的。而上文提到的binlog就是屬于MySQL服務器的日志,binlog也叫二進制日志,記錄了所有對MySQL所做的更改。
基于行、語句的復制方式跟binlog的存儲方式有關系。binlog有三種存儲格式,分別是Statement、Row和Mixed。
Statement 基于語句,只記錄對數據做了修改的SQL語句,能夠有效的減少binlog的數據量,提高讀取、基于binlog重放的性能
Row 只記錄被修改的行,所以Row記錄的binlog日志量一般來說會比Statement格式要多。基于Row的binlog日志非常完整、清晰,記錄了所有數據的變動,但是缺點是可能會非常多,例如一條update語句,有可能是所有的數據都有修改;再例如alter table之類的,修改了某個字段,同樣的每條記錄都有改動。
Mixed Statement和Row的結合,怎么個結合法呢。例如像update或者alter table之類的語句修改,采用Statement格式。其余的對數據的修改例如update和delete采用Row格式進行記錄。
為什么會有這么多方式呢?因為Statement只會記錄SQL語句,但是并不能保證所有情況下這些語句在從庫上能夠正確的被重放出來。因為可能順序不對。
MySQL什么時候會記錄binlog呢?是在事務提交的時候,并不是按照語句的執行順序來記錄,當記錄完binlog之后,就會通知底層的存儲引擎提交事務,所以有可能因為語句順序錯誤導致語句出錯。
1.2 查看binlog
這里拿MySQL 5.6舉例子,binlog默認是處于關閉狀態的。我們可以通過命令show variables like '%log_bin%' 來查看關于binlog的配置。
默認配置
log_bin代表是否開啟了binlog,其默認值為OFF。
log_bin 代表是否開啟了binlog,其默認值為OFF
log_bin_basename binlog存儲文件的完整名稱,會在默認的文件名后面添加上遞增的序號,就例如mysql-bin.000001
log_bin_index binlog索引文件名稱,例如mysql-bin.index
sql_log_bin 在binlog開啟的時候,可以禁用當前session的binlog
你可以在MySQL中通過命令show binary logs查看所有的binlog文件
圖片
查看binlog
知道了有哪些文件之后我們可以來看看binlog文件中的內容,可以在MySQL通過show binlog events命令來查看。
show binglog events 查看第一個binlog文件,我們也可以通過in參數來指定,假設我們想看的文件名是mysql-bin.000001,那么可以使用命令show binlog events in 'mysql-bin.000001'來查看指定的binlog文件
查看binlog
接下來我們來看看我們在MySQL中的操作所對應的binlog內容分別是什么。
初始化
我們上面提到過,binlog是由一個一個的event組成的。從MySQL 5.0開始,binlog的第一個event都為Format_desc,位于圖中的Event_type那一列。可以看到內容為Server ver;5.6.50-log, Binlog ver: 4,說明當前使用的MySQL版本為5.6.50,Binlog的版本是V4。
創建數據庫
然后我創建了一個名為student的DB,其Event_type是Query,這個event的內容為CREATE DATABASE student DEFAULT CHARACTER SET = utf8mb4,一個建庫語句。
新建表
然后我創建了一個名為student的表,Event_type也是Query,內容為use student; CREATE TABLE student (id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT),一個建表語句。
插入數據
然后我們執行INSERT語句給該表插入兩行數據,再次查看binlog。
INSERT INTO `student` (`id`, `name`) VALUES (NULL, '張三'); INSERT INTO `student` (`id`, `name`) VALUES (NULL, '李四');
image-20210106123550397
可以看到每次INSERT都會開啟一個事務,你可能會疑惑,我們只是簡單的執行了INSERT語句,沒有顯示的開啟事務。那為什么會有事務產生呢?
這是因為MySQL采用了自動提交(AUTOCOMMIT)的機制,我使用的InnoDB存儲引擎,是支持事務的,所有的用戶活動都發生在事務中。我們可以通過show variables like '%AUTOCOMMIT%';命令查看,如果結果是ON則代表是開啟的。
1.3 復制的核心步驟
我們假設主庫已經開啟了binlog,并正常的記錄binlog。
首先從庫啟動I/O線程,跟主庫建立客戶端連接。
主庫啟動binlog dump線程,讀取主庫上的binlog event發送給從庫的I/O線程,I/O線程獲取到binlog event之后將其寫入到自己的Relay Log中。
然后從庫啟動SQL線程,將Relay中的數據進行重放,完成從庫的數據更新。
總結來說,主庫上只會有一個線程,而從庫上則會有兩個線程。
主從復制流程
1.4 Relay Log
relay log其實和binlog沒有太大的區別,在MySQL 4.0 之前是沒有Relay Log這部分的,整個過程中只有兩個線程。但是這樣也帶來一個問題,那就是復制的過程需要同步的進行,很容易被影響,而且效率不高。例如主庫必須要等待從庫讀取完了才能發送下一個binlog事件。這就有點類似于一個阻塞的信道和非阻塞的信道。
阻塞信道
阻塞信道就跟你在柜臺一樣,你要遞歸柜員一個東西,但是你和柜員之間沒有可以放東西的地方,你就只能一直把文件拿著,直到柜員接手;而非阻塞信道就像你們之間有個地方可以放文件,你就直接放上去就好了,不用等柜員接手。
引入了Relay Log之后,讓原本同步的獲取事件、重放事件解耦了,兩個步驟可以異步的進行,Relay Log充當了緩沖區的作用。Relay Log有一個relay-log.info的文件,用于記錄當前復制的進度,下一個事件從什么Pos開始寫入,該文件由SQL線程負責更新。
1.5 Relay Log核心參數
接下來讓我們了解一下Relay Log的核心參數。
max_relay_log_size 中繼日志的最大size,默認值0,如果為0就會取默認的size 1G,否則就為設置的值
relay_log 定義relay的名稱,默認為主機名+relay-bin,例如像hostname-relay-bin
relay_log_basename 中繼日志的全路徑,即路徑 + 文件名,例如/path/to/hostname-relay-bin,最大長度為256
relay_log_index 定義中繼日志的索引文件的全路徑,同樣其最大的長度為256. 其默認值為hostname + relay-bin.index,例如/path/to/hostname-relay-bin.index
relay_log_info_file 定義relay-log.info文件的名稱
relay_log_info_repository 存放relay log重放的數據的方式,可以設置為FILE和TABLE。FILE代表將中繼日志重放的數據記錄在relay-info.log中,TABLE則將其存放在slave_relay_log_info這張表里。
relay_log_purge 是否自動清空不需要的中繼日志,默認值為ON
relay_log_recovery 當從庫宕機后,如果relay log損壞了導致部分的中繼日志沒有進行同步,則自動放棄所有未進行重放的中繼日志,并從主庫重新獲取,默認值為OFF
relay_log_space_limit 設置中繼日志的最大值,防止寫滿磁盤。但是不建議設置這個值,建議還是給中繼日志需要的空間,0就是不限制,0也是默認值
sync_relay_log 用于控制中繼日志寫入磁盤的變量,假設值為n,那么在中繼日志每接受n次binlog事件之后就會調用fdatasync()函數將中繼日志強制的刷入磁盤;相反,如果值為0,則寫入OS的緩沖區內,由OS調度決定何時將中繼日志刷入磁盤,這樣一來如果在沒有刷入之前報錯了,那么中繼日志就會丟失。默認值是10000,也就是每向中繼日志中寫入1w次binlog事件就將中繼日志強制的刷入磁盤。
sync_relay_log_info 該參數的影響跟參數relay_log_info_repository有一定關系,同時也跟是否使用支持事務的存儲引擎有關系。該值默認也是10000.
relay_log_info_repository為FILE,假設設置的值為N,那么每N次事務都會都會調用fdatasync()強制將relay-log.info刷入磁盤
relay_log_info_repository為TABLE,如果使用了支持事務的引擎,則該表每次事務結束都會被更新;如果沒有使用事務引擎則會在寫入N個binlog事件的時候更新該表。
relay_log_info_repository為FILE,MySQL不會調用fdatasync(),而是將刷入磁盤的調度交給OS;
relay_log_info_repository為TABLE,如果使用了支持事務的存儲引擎,則每次事務的時候該表都會被更新;如果沒有使用事務引擎,則永遠不會被更新
當sync_relay_log_info為0時
當sync_relay_log_info大于0時
2. 復制模型
平常的開發中,其實很少說一上來就直接搞主從架構的。費時間、費錢還引入了額外的復雜度,最后發現投入了這么多一個單MySQL服務器就完全能handle。
這就跟一個產品的架構迭代是一樣的,剛剛起步的時候一個單體應用足夠了。當你的業務擴展,請求膨脹,單體無法抗住壓力了,就會考慮開始部署多實例,開始采用微服務架構去做橫向擴展、負載均衡。
2.1 一主多從
當然你也可以把它當成一主一從。
這是最簡單的模型,特別適合少量寫、大量讀的情況。讀請求被分到了各個從庫上,有效的幫主庫分散了壓力,能夠提升讀并發。當然,你也可以只是把從庫當成一個災備庫,除了主從復制之外,沒有其他任何的請求和數據傳輸。
甚至你可以把其中一個備庫作為你的預發環境的數據庫,當然,這說到底還是直接動了生產環境的數據庫,是一種過于理想的用途,因為這還涉及到生產環境數據庫的數據敏感性。不是所有人都能夠接觸到的,需要有完善的權限機制。
MySQL一主多從
值得注意的是,如果有n個從庫,那么主庫上就會有n個binlog dump線程。如果這個n比較大的話在復制的時候可能會造成主庫的性能抖動。所以在從庫較多的情況下可以采用級聯復制。
2.2 級聯復制
級聯復制用大白話說就是套娃。
本來從庫B、C、D、E、F、G都是復制的主庫A,但是現在由于A的壓力比較大,就不這么干了,調整成了如下的模式。
B、C復制A
D、E復制B
F、G復制C
MySQL級聯復制
這就叫級聯復制,開啟瘋狂套娃模式。你甚至會覺得這種套娃很眼熟,在Redis主從復制中也可以采用級聯模式, slave去復制另一個slave。
級聯復制的好處在于很大程度上減輕了主庫的壓力,主庫只需要關心與其有直接復制關系的從庫,剩下的復制則交給從庫即可。相反,由于是這種層層嵌套的關系,如果在較上層出現了錯誤,會影響到掛在該服務器下的所有子庫,這些錯誤的影響效果被放大了。
2.3 主主復制
顧名思義,就是兩個主庫相互復制,客戶端可以對任意一臺主庫進行寫操作。任何一臺主庫服務器上的數據發生了變化都會同步到另一臺服務器上去。有點類似于Eureka Server的雙節點模式,兩個注冊中心相互注冊。這樣一來,任何一臺掛了都不會對系統產生影響。
而且主主復制可以打破數據庫性能瓶頸,一個很酷的功能——橫向擴展。為什么說很酷呢,如果DB能做到橫向擴展,那很多被數據庫并發所限制的瓶頸都可以被突破,然而...
但是主主復制其實并不可靠,兩邊的數據沖突的可能性很大。例如復制停止了,系統仍然在向兩個主庫中寫入數據,也就是說一部分數據在A,另一部分的數據在B,但是沒有相互復制,且數據也不同步了。要修復這部分數據的難度就會變得相當大。
所以我認為雙主的更多的意義在于HA,而不是負載均衡。
2.4 主、被動的主主復制
同樣還是雙主的結構,但是區別在于其中一臺是只讀的被動服務器,客戶端不會向該庫進行寫操作。
其用途在哪里呢?例如我們要在不中斷服務的前提下對MySQL進行維護、優化,舉個例子——修改表結構。假設我們有兩個數據庫,主庫A和被動主庫B,注意此處的被動主庫是只讀的,我們先停止A對B的復制,也就是停掉A上的SQL線程。
主主停止復制
這樣一來,我們之后在B上執行的非常耗時、可能需要鎖表的操作就不會立即同步到A上來。因為此時A正在對外提供服務,所以不能使其收到影響,但是由于采用的是異步的復制模式,所以Relay Log還是繼續由I/O線程寫入,只是不去進行重放。
然后我們在B上執行此次的維護操作,注意,此時A上面發生的更新還是會正常的同步到B來。執行完后交換讀寫的角色。也就是讓A變成只讀的被動主庫,而B變為主動主庫對外提供服務。
重新開啟SQL線程
然后重新開啟SQL線程,A開始去對之前Relay Log中積累的event進行重放。雖然A此時可能會阻塞住,但是A已經沒有對外提供服務了,所以沒有問題。
主、被動下的主主模式的好處大家也就清楚了,可以在不停止服務的情況下去做數據庫的結構更新,其次可以在主庫發生故障的情況下,快速的切換,保證數據庫的HA。
3. 復制方式
上文我們不止一次的提到了復制是異步的,接下來我們來了解一下MySQL的主從復制都有哪些方式。
3.1 異步復制
首先就是異步,這也是MySQL默認的方式。在異步復制下,主庫不會主動的向從庫發送消息,而是等待從庫的I/O線程建立連接,然后主庫創建binlog dump線程,把binlog event發送給I/O線程,流程如下圖。
MySQL復制模式
主庫在執行完自己的事務、記錄完binlog之后就會直接返回,不會與客戶端確認任何結果。然后后續由binlog dump線程異步的讀取binlog,然后發送給從庫。處理請求和主從復制是兩個完全異步化的過程。
3.2 同步復制
同步模式則是,主庫執行一個事務,那么主庫必須等待所有的從庫全部執行完事務返回commit之后才能給客戶端返回成功,
同步復制
值得注意的是,主庫會直接提交事務,而不是等待所有從庫返回之后再提交。MySQL只是延遲了對客戶端的返回,并沒有延后事務的提交。
同步模式用腳趾頭想知道性能會大打折扣,它把客戶端的請求和主從復制耦合在了一起,如果有某個從庫復制線程執行的慢,那么對客戶端的響應也會慢很多。
3.3 半同步復制
半同步相對于同步的區別在于,同步需要等待所有的從庫commit,而半同步只需要一個從庫commit就可以返回了。如果超過默認的時間仍然沒有從庫commit,就會切換為異步模式再提交。客戶端也不會一直去等待了。
MySQL復制模式
因為即使后面主庫宕機了,也能至少保證有一個從庫節點是可以用的,此外還減少了同步時的等待時間。
4. 復制中的數據一致性
我們在1.3中討論了復制的核心步驟,看似很簡單的一個流程,主庫的binlog dump去讀取binlog,然后從庫的I/O線程去讀取、寫入Relay Log,進而從庫的SQL線程再讀取Relay Log進行重放。
那如果I/O線程復制到一半自己突然掛掉了呢?又或者復制到一半主庫宕機了呢?如果和保證數據一致性的呢?
我們上面提到過,有一個relay-log.info的文件,用于記錄當前從庫正在復制的binlog和寫入的Relay Log的Pos,只要這個文件還在,那么當從庫意外重啟之后,就會重新讀取文件,從上次復制的地方開始繼續復制。這就跟Redis中的主從復制類似,雙方要維護一個offset,通過對比offset,來進行psync增量數據同步。
但是在MySQL 5.5以及之前,都只能將復制的進度記錄在relog-log.info文件中。換句話說,參數relay_log_info_repository只支持FILE,可以再回到上面的1.5 Relay Log核心參數看一下。所以只有在sync_relay_log_info次事務之后才會把relay-log.info文件刷入磁盤。
如果在刷入磁盤之前從庫掛了,那么重啟之后就會發現SQL線程實際執行到位置和數據庫記錄的不一致,數據一致性的問題就這么產生了。
所以在MySQL 5.6時,參數relay_log_info_repository支持了TABLE,這樣一來我們就可以將復制的進度放在系統的mysql.slave_relay_log_info表里去,并且把更新進度、SQL線程執行用戶事務綁定成一個事務執行。即使slave宕機了,我們也可以通過MySQL內建的崩潰恢復機制來使實際執行的位置和數據庫保存的進度恢復到一致。
其次還有上面提到的半同步復制,主庫會先提交事務,然后等待從庫的返回,再將結果返回給客戶端,但是如果在主庫等待的時候,從庫掛了呢?
此時主庫上由于事務已經提交了,但是從庫上卻沒有這個數據。所以在MySQL 5.7時引入了無損半同步復制,增加了參數rpl_semi_sync_master_wait_point的值,在MySQL 5.7中值默認為after_sync,在MySQL 5.6中默認值為after_commit。
after_sync 主庫先不提交事務,等待某一個從庫返回了結果之后,再提交事務。這樣一來,如果從庫在沒有任何返回的情況下宕機了,master這邊也無法提交事務。主從仍然是一致的
after_commit 與之前討論的一樣,主庫先提交事務,等待從庫返回結果再通知客戶端
看完上述內容,你們掌握MySQL中主從復制的原理是什么的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。