歡迎微信關注「SH的全棧筆記」mysql
首先主從複製是什麼?簡單來講是讓一臺MySQL服務器去複製另外一臺MySQL的數據,使兩個服務器的數據保持一致。web
這種方式與Redis的主從複製的思路沒有太大的出入。若是你對Redis的主從複製感興趣能夠去看看《Redis的主從複製》。那既然Redis和MySQL都採用了複製這種方式,主從複製所帶來的意義是什麼呢?sql
經過複製功能,構建一個或者多個從庫,能夠提升數據庫的高可用性、可擴展性,同時實現負載均衡。當主庫發生故障時,能夠快速的切到其某一個從庫,並將該從庫提高爲主庫,由於數據都同樣,因此不會影響系統的運行;當MySQL服務器須要扛住更多的讀請求時,能夠把讀請求的流量分流到各個從庫上去,寫請求則轉發給主庫,造成讀寫分離的架構,來提供更好的讀擴展和請求的負載均衡。數據庫
讀寫分離的架構應用的其實很是普遍,就好比MySQL,還有Redis,以及咱們熟悉的Zookeeper,Zookeeper的Follower收到讀請求不會本身處理,而是會將讀請求轉發給Leader,感興趣的能夠本身下來了解一下,這裏就不偏題了。服務器
MySQL的主從複製支持兩種方式:微信
基於語句的複製在MySQL3.23中就已經有了,而基於語句的方式則在5.1中才實現。其本質都是基於主庫的binlog來實現的,主庫記錄binlog,而後從庫將binlog在本身的服務器上重放,從而保證了主、從的數據一致性。session
MySQL中日誌分爲兩個維度,一個是MySQL服務器的,一個是底層存儲引擎的。而上文提到的binlog就是屬於MySQL服務器的日誌,binlog也叫二進制日誌,記錄了全部對MySQL所作的更改。架構
基於行、語句的複製方式跟binlog的存儲方式有關係。 binlog有三種存儲格式,分別是Statement、Row和Mixed。併發
update
語句,有多是全部的數據都有修改;再例如
alter table
之類的,修改了某個字段,一樣的每條記錄都有改動。
update
或者
alter table
之類的語句修改,採用Statement格式。其他的對數據的修改例如
update
和
delete
採用Row格式進行記錄。
爲何會有這麼多方式呢?由於Statement只會記錄SQL語句,可是並不能保證全部狀況下這些語句在從庫上可以正確的被重放出來。由於可能順序不對。負載均衡
MySQL何時會記錄binlog呢?是在事務提交的時候,並非按照語句的執行順序來記錄,當記錄完binlog以後,就會通知底層的存儲引擎提交事務,因此有可能由於語句順序錯誤致使語句出錯。
這裏拿MySQL 5.6舉例子,binlog默認是處於關閉狀態的。咱們能夠經過命令show variables like '%log_bin%'
來查看關於binlog的配置。
log_bin
表明是否開啓了binlog,其默認值爲OFF
。
OFF
mysql-bin.000001
mysql-bin.index
你能夠在MySQL中經過命令show binary logs
查看全部的binlog文件
知道了有哪些文件以後咱們能夠來看看binlog文件中的內容,能夠在MySQL經過show binlog events
命令來查看。
show binglog events 查看第一個binlog文件,咱們也能夠經過
in
參數來指定,假設咱們想看的文件名是mysql-bin.000001
,那麼可使用命令show binlog events in 'mysql-bin.000001'
來查看指定的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, '李四');
能夠看到每次INSERT都會開啓一個事務,你可能會疑惑,咱們只是簡單的執行了INSERT語句,沒有顯示的開啓事務。那爲何會有事務產生呢?
這是由於MySQL採用了自動提交(AUTOCOMMIT)的機制,我使用的InnoDB存儲引擎,是支持事務的,全部的用戶活動都發生在事務中。咱們能夠經過show variables like '%AUTOCOMMIT%';
命令查看,若是結果是ON
則表明是開啓的。
咱們假設主庫已經開啓了binlog,並正常的記錄binlog。
首先從庫啓動I/O線程,跟主庫創建客戶端鏈接。
主庫啓動binlog dump線程,讀取主庫上的binlog event發送給從庫的I/O線程,I/O線程獲取到binlog event以後將其寫入到本身的Relay Log中。
而後從庫啓動SQL線程,將Relay中的數據進行重放,完成從庫的數據更新。
總結來講,主庫上只會有一個線程,而從庫上則會有兩個線程。
relay log其實和binlog沒有太大的區別,在MySQL 4.0 以前是沒有Relay Log這部分的,整個過程當中只有兩個線程。可是這樣也帶來一個問題,那就是複製的過程須要同步的進行,很容易被影響,並且效率不高。例如主庫必需要等待從庫讀取完了才能發送下一個binlog事件。這就有點相似於一個阻塞的信道和非阻塞的信道。
阻塞信道就跟你在櫃檯同樣,你要遞歸櫃員一個東西,可是你和櫃員之間沒有能夠放東西的地方,你就只能一直把文件拿着,直到櫃員接手;而非阻塞信道就像大家之間有個地方能夠放文件,你就直接放上去就行了,不用等櫃員接手。
引入了Relay Log以後,讓本來同步的獲取事件、重放事件解耦了,兩個步驟能夠異步的進行,Relay Log充當了緩衝區的做用。Relay Log有一個relay-log.info
的文件,用於記錄當前複製的進度,下一個事件從什麼Pos開始寫入,該文件由SQL線程負責更新。
接下來讓咱們瞭解一下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
.
當sync_relay_log_info
爲0時
relay_log_info_repository
爲FILE,MySQL不會調用fdatasync(),而是將刷入磁盤的調度交給OS;
relay_log_info_repository
爲TABLE,若是使用了支持事務的存儲引擎,則每次事務的時候該表都會被更新;若是沒有使用事務引擎,則永遠不會被更新
當sync_relay_log_info
大於0時
relay_log_info_repository
爲FILE,假設設置的值爲N,那麼
每N次事務都會都會調用fdatasync()強制將relay-log.info刷入磁盤
relay_log_info_repository
爲TABLE,若是使用了支持事務的引擎,則該表
每次事務結束都會被更新;若是沒有使用事務引擎則會在
寫入N個binlog事件的時候更新該表。
日常的開發中,其實不多說一上來就直接搞主從架構的。費時間、費錢還引入了額外的複雜度,最後發現投入了這麼多一個單MySQL服務器就徹底能handle。
這就跟一個產品的架構迭代是同樣的,剛剛起步的時候一個單體應用足夠了。當你的業務擴展,請求膨脹,單體沒法抗住壓力了,就會考慮開始部署多實例,開始採用微服務架構去作橫向擴展、負載均衡。
固然你也能夠把它當成一主一從。
這是最簡單的模型,特別適合少許寫、大量讀的狀況。讀請求被分到了各個從庫上,有效的幫主庫分散了壓力,可以提高讀併發。固然,你也能夠只是把從庫當成一個災備庫,除了主從複製以外,沒有其餘任何的請求和數據傳輸。
甚至你能夠把其中一個備庫做爲你的預發環境的數據庫,固然,這說到底仍是直接動了生產環境的數據庫,是一種過於理想的用途,由於這還涉及到生產環境數據庫的數據敏感性。不是全部人都可以接觸到的,須要有完善的權限機制。
值得注意的是,若是有n個從庫,那麼主庫上就會有n個binlog dump線程。若是這個n比較大的話在複製的時候可能會形成主庫的性能抖動。因此在從庫較多的狀況下能夠採用級聯複製。
級聯複製用大白話說就是套娃。
原本從庫B、C、D、E、F、G都是複製的主庫A,可是如今因爲A的壓力比較大,就不這麼幹了,調整成了以下的模式。
這就叫級聯複製,開啓瘋狂套娃模式。你甚至會以爲這種套娃很眼熟,在Redis主從複製中也能夠採用級聯模式, slave去複製另外一個slave。
級聯複製的好處在於很大程度上減輕了主庫的壓力,主庫只須要關心與其有直接複製關係的從庫,剩下的複製則交給從庫便可。相反,因爲是這種層層嵌套的關係,若是在較上層出現了錯誤,會影響到掛在該服務器下的全部子庫,這些錯誤的影響效果被放大了。
顧名思義,就是兩個主庫相互複製,客戶端能夠對任意一臺主庫進行寫操做。任何一臺主庫服務器上的數據發生了變化都會同步到另外一臺服務器上去。有點相似於Eureka Server的雙節點模式,兩個註冊中心相互註冊。這樣一來,任何一臺掛了都不會對系統產生影響。
並且主主複製能夠打破數據庫性能瓶頸,一個很酷的功能——橫向擴展。爲何說很酷呢,若是DB能作到橫向擴展,那不少被數據庫併發所限制的瓶頸均可以被突破,然而...
可是主主複製其實並不可靠,兩邊的數據衝突的可能性很大。例如複製中止了,系統仍然在向兩個主庫中寫入數據,也就是說一部分數據在A,另外一部分的數據在B,可是沒有相互複製,且數據也不一樣步了。要修復這部分數據的難度就會變得至關大。
因此我認爲雙主的更多的意義在於HA,而不是負載均衡。
一樣仍是雙主的結構,可是區別在於其中一臺是只讀的被動服務器,客戶端不會向該庫進行寫操做。
其用途在哪裏呢?例如咱們要在不中斷服務的前提下對MySQL進行維護、優化,舉個例子——修改表結構。假設咱們有兩個數據庫,主庫A和被動主庫B,注意此處的被動主庫是只讀的,咱們先中止A對B的複製,也就是停掉A上的SQL線程。
這樣一來,咱們以後在B上執行的很是耗時、可能須要鎖表的操做就不會當即同步到A上來。由於此時A正在對外提供服務,因此不能使其收到影響,可是因爲採用的是異步的複製模式,因此Relay Log仍是繼續由I/O線程寫入,只是不去進行重放。
而後咱們在B上執行這次的維護操做,注意,此時A上面發生的更新仍是會正常的同步到B來。執行完後交換讀寫的角色。也就是讓A變成只讀的被動主庫,而B變爲主動主庫對外提供服務。
而後從新開啓SQL線程,A開始去對以前Relay Log中積累的event進行重放。雖然A此時可能會阻塞住,可是A已經沒有對外提供服務了,因此沒有問題。
主、被動下的主主模式的好處你們也就清楚了,能夠在不中止服務的狀況下去作數據庫的結構更新,其次能夠在主庫發生故障的狀況下,快速的切換,保證數據庫的HA。
上文咱們不止一次的提到了複製是異步的
,接下來咱們來了解一下MySQL的主從複製都有哪些方式。
首先就是異步,這也是MySQL默認的方式。在異步複製下,主庫不會主動的向從庫發送消息,而是等待從庫的I/O線程創建鏈接,而後主庫建立binlog dump
線程,把binlog event發送給I/O線程,流程以下圖。
主庫在執行完本身的事務、記錄完binlog以後就會直接返回,不會與客戶端確認任何結果。而後後續由binlog dump線程異步的讀取binlog,而後發送給從庫。處理請求和主從複製是兩個徹底異步化的過程。
同步模式則是,主庫執行一個事務,那麼主庫必須等待全部的從庫所有執行完事務返回commit以後才能給客戶端返回成功,
值得注意的是,主庫會直接提交事務,而不是等待全部從庫返回以後再提交。MySQL只是延遲了對客戶端的返回,並無延後事務的提交。
同步模式用腳趾頭想知道性能會大打折扣,它把客戶端的請求和主從複製耦合在了一塊兒,若是有某個從庫複製線程執行的慢,那麼對客戶端的響應也會慢不少。
半同步相對於同步的區別在於,同步須要等待全部的從庫commit,而半同步只須要一個從庫commit就能夠返回了。若是超過默認的時間仍然沒有從庫commit,就會切換爲異步模式再提交。客戶端也不會一直去等待了。
由於即便後面主庫宕機了,也能至少保證有一個從庫節點是能夠用的,此外還減小了同步時的等待時間。
咱們在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
。
好了以上就是本篇博客的所有內容了,若是你以爲這篇文章對你有幫助,還麻煩點個贊,關個注,分個享,留個言。
歡迎微信搜索關注【SH的全棧筆記】,查看更多相關文章