面試高頻:MySQL是怎么保證高可用的?
微信搜 歡少的成長之路
介紹
大家好,我是Leo,從事Java后端開發(fā)。之前的文章大概介紹了主從庫的數(shù)據(jù)一致性問題,通過分析binlog的三種格式的優(yōu)缺點(diǎn)以及應(yīng)用性效率。介紹了主從同步的循環(huán)復(fù)制問題以及解決方案。如果不太清楚的小伙伴可以【關(guān)注公眾號(hào):歡少的成長之路】。今天這里主要介紹一下MySQL高可用這一塊的知識(shí)。

思路
根據(jù)讀者和用戶的反饋,畫了一個(gè)寫作思路圖。通過此圖可以更好的分析出當(dāng)前文章的寫作知識(shí)點(diǎn)??梢愿斓膸椭x者在最短時(shí)間內(nèi)判斷是否為有效文章!

正題

主從延遲
先說一下為什么會(huì)有主庫延遲這個(gè)問題。隨著互聯(lián)網(wǎng)業(yè)務(wù)數(shù)量量越來越大,伴隨著我們數(shù)據(jù)庫的DB能力也要隨之增強(qiáng)。當(dāng)下最貼合生產(chǎn)數(shù)據(jù)庫模式的應(yīng)該也就是主從庫+讀寫分離+緩存中間件等一系列的解決方案。我們這里只講一下MySQL的本身問題。
如上圖所示,A是主庫,B是從庫。提到高可用問題,肯定不能A一直是主庫,B一直是從庫。所以就存在了主從切換的問題。下面介紹一下切換的流程。
主從A在執(zhí)行一個(gè)事務(wù)的時(shí)候,寫入binlog日志,我們把這個(gè)步驟記為T1。
之后主庫A為了同步,把binlog日志傳給從庫B,備庫B接收完這個(gè)binlog我們記為T2。
最終備庫B執(zhí)行這個(gè)binlog同步數(shù)據(jù),我們記為T3
所謂的主從延遲就是在執(zhí)行同一個(gè)事務(wù)的時(shí)候,在備庫執(zhí)行完成的時(shí)間和主庫執(zhí)行完成的時(shí)間之間的差值,也就是 T3-T1。
你可以在備庫上執(zhí)行 show slave status 命令,它的返回結(jié)果里面會(huì)顯示 seconds_behind_master,用于表示當(dāng)前備庫延遲了多少秒。
可以看到,seconds_behind_master 就是就是主庫執(zhí)行的時(shí)間 - 從庫執(zhí)行的時(shí)間差。這個(gè)值的精準(zhǔn)度是精確到秒的。這里有個(gè)很有意思的函數(shù),我們介紹一下。這個(gè)函數(shù)也幫我們解決了時(shí)間一致性的問題
SELECT UNIX_TIMESTAMP()
主從庫在做數(shù)據(jù)同步的時(shí)候,會(huì)通過以上函數(shù)來獲取當(dāng)前主庫的系統(tǒng)時(shí)間,如果這時(shí)候發(fā)現(xiàn)主庫的系統(tǒng)時(shí)間與自己不一致,備庫在執(zhí)行 seconds_behind_master 計(jì)算的時(shí)候會(huì)自動(dòng)扣掉這個(gè)差值。
在網(wǎng)絡(luò)正常的情況下,主從傳給備庫的binlog是很短的。所以主要延遲來源于從庫接收完binlog和執(zhí)行完這個(gè)事務(wù)之間的時(shí)間差。
主從延遲來源
硬件問題
第一種可能就是硬件問題,一些小公司會(huì)考慮把多個(gè)備庫放在一臺(tái)機(jī)器上,把少量的主庫放在一臺(tái)機(jī)器上。認(rèn)為讀庫的需求比寫庫小。其實(shí)不是這樣的。從庫要做到數(shù)據(jù)同步一致性,所以更新請(qǐng)求的IPOS的壓力是差不多的。
解決方案: 我們一般采用對(duì)稱部署。就是主庫與從庫采用相同規(guī)格的服務(wù)器進(jìn)行部署使用!
壓力過大
主庫提供寫的能力,所以很多人員對(duì)主庫一般都是小心翼翼的。導(dǎo)致測(cè)試的請(qǐng)求都打到了備庫上,導(dǎo)致備庫壓力過大,在處理數(shù)據(jù)的時(shí)候產(chǎn)生延遲。
解決方案: 這種情況可以這樣處理
一主多從。除了備庫外,可以多接幾個(gè)從庫,讓這些從庫來分擔(dān)讀的壓力。
通過 binlog 輸出到外部系統(tǒng),比如 Hadoop 這類系統(tǒng),讓外部系統(tǒng)提供統(tǒng)計(jì)類查詢的能力。
大事務(wù)
這種情況就是非常好理解的了。比如我們?cè)谝粋€(gè)主庫刪除一些歷史數(shù)據(jù)。主庫上必須等操作執(zhí)行完全之后才會(huì)寫入binlog,然后再傳給從庫。所以如果主庫上執(zhí)行10分鐘,那么備份到從庫上也會(huì)執(zhí)行10分鐘。
這里理解不了的話,我們可以查看上一篇的binlog組成。主要有三種格式,row,statement,mixed。在執(zhí)行binlog的時(shí)候會(huì)執(zhí)行大量的數(shù)據(jù)。
大表DDL
大表DDL的話,我們之前在描述刪除《表數(shù)據(jù),為什么空間沒變》的時(shí)候大概介紹了一些。如果有不清楚的可以關(guān)注【微信公眾號(hào):歡少的成長之路】。
如果對(duì)很大的表來說,操作DDL是很消耗IO和CPU資源的。所以我們一般建議使用gh-ost 方案進(jìn)行。
從庫復(fù)制能力
最后一種原因就是從庫的并行復(fù)制能力。這個(gè)知識(shí)點(diǎn),我們?cè)诤罄m(xù)再進(jìn)行介紹,東西比較多!
可靠性優(yōu)先策略
介紹一下MySQL的可靠性優(yōu)先策略。這里主要處理的是 主從庫的選擇問題。具體的流程如下
判斷備庫B的
seconds_behind_master是否小于某個(gè)值,如果大于某個(gè)值的話延遲太大會(huì)影響業(yè)務(wù)數(shù)據(jù)的,所以一定要小于某個(gè)值的時(shí)候才可以繼續(xù)下一步把主庫A改成只讀狀態(tài),readonly改為true
再判斷
seconds_behind_master的值,直到這個(gè)值變成0為止。把備庫B改成讀寫狀態(tài),也就是把readonly改為flase
最后把業(yè)務(wù)的請(qǐng)求都打到B上

如上圖所示:SBM就是seconds_behind_master的縮寫。
可以看到,這個(gè)過程是有不可用時(shí)間的。當(dāng)把主庫A改成readonly的時(shí)候,同步給從庫B的時(shí)候。從庫B也改成了readonly。那么這段時(shí)間主從庫都是不可寫的。直接從庫B恢復(fù)完binlog才正常執(zhí)行。
最耗時(shí)的是在從庫B執(zhí)行binlog日志的時(shí)候,這也是為什么需要第一步先判斷SBM的原因。只有確保延遲足夠小。在后續(xù)中才會(huì)縮小誤差。
可用性優(yōu)先策略
除了可靠性優(yōu)先策略,還有一個(gè)策略是可用性優(yōu)先策略。
如果我不把數(shù)據(jù)同步完成之后,就直接把訪問切過去,那幾乎就沒有不可用時(shí)間了。我們把這個(gè)過程稱為可用性優(yōu)先策略。但是另一個(gè)弊端就暴露出來了,無法保證數(shù)據(jù)一致問題。
舉例說明一下
假設(shè)有一個(gè)表 t,并且插入三行數(shù)據(jù):
mysql> CREATE TABLE `t` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`c` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(c) values(1),(2),(3);
這個(gè)表定義了一個(gè)自增主鍵 id,初始化數(shù)據(jù)后,主庫和備庫上都是 3 行數(shù)據(jù)。接下來,業(yè)務(wù)人員要繼續(xù)在表 t 上執(zhí)行兩條插入語句的命令,依次是:
insert into t(c) values(4);
insert into t(c) values(5);
假設(shè),現(xiàn)在主庫上其他的數(shù)據(jù)表有大量的更新,導(dǎo)致主備延遲達(dá)到 5 秒。在插入一條 c=4 的語句后,發(fā)起了主備切換。如下圖,可用性優(yōu)先策略且binlog_format=mixed的流程

步驟 2 中,主庫 A 執(zhí)行完 insert 語句,插入了一行數(shù)據(jù)(4,4),之后開始進(jìn)行主備切換。
步驟 3 中,由于主備之間有 5 秒的延遲,所以備庫 B 還沒來得及應(yīng)用“插入 c=4”這個(gè)中轉(zhuǎn)日志,就開始接收客戶端“插入 c=5”的命令。
步驟 4 中,備庫 B 插入了一行數(shù)據(jù)(4,5),并且把這個(gè) binlog 發(fā)給主庫 A。
步驟 5 中,備庫 B 執(zhí)行“插入 c=4”這個(gè)中轉(zhuǎn)日志,插入了一行數(shù)據(jù)(5,4)。而直接在備庫 B 執(zhí)行的“插入 c=5”這個(gè)語句,傳到主庫 A,就插入了一行新數(shù)據(jù)(5,5)。
最后的結(jié)果就是,主庫 A 和備庫 B 上出現(xiàn)了兩行不一致的數(shù)據(jù)??梢钥吹?,這個(gè)數(shù)據(jù)不一致,是由可用性優(yōu)先流程導(dǎo)致的。
那么,如果我還是用可用性優(yōu)先策略,但設(shè)置 binlog_format=row,情況又會(huì)怎樣呢?
因?yàn)?row 格式在記錄 binlog 的時(shí)候,會(huì)記錄新插入的行的所有字段值,所以最后只會(huì)有一行不一致。而且,兩邊的主備同步的應(yīng)用線程會(huì)報(bào)錯(cuò) duplicate key error 并停止。也就是說,這種情況下,備庫 B 的 (5,4) 和主庫 A 的 (5,5) 這兩行數(shù)據(jù),都不會(huì)被對(duì)方執(zhí)行。

使用 row 格式的 binlog 時(shí),數(shù)據(jù)不一致的問題更容易被發(fā)現(xiàn)。而使用 mixed 或者 statement 格式的 binlog 時(shí),數(shù)據(jù)很可能悄悄地就不一致了。如果你過了很久才發(fā)現(xiàn)數(shù)據(jù)不一致的問題,很可能這時(shí)的數(shù)據(jù)不一致已經(jīng)不可查,或者連帶造成了更多的數(shù)據(jù)邏輯不一致。
主備切換的可用性優(yōu)先策略會(huì)導(dǎo)致數(shù)據(jù)不一致。因此,大多數(shù)情況下,我都建議你使用可靠性優(yōu)先策略。畢竟對(duì)數(shù)據(jù)服務(wù)來說的話,數(shù)據(jù)的可靠性一般還是要優(yōu)于可用性的。
按照可靠性優(yōu)先的思路,異常切換會(huì)是什么效果?
假設(shè),主庫 A 和備庫 B 間的主備延遲是 30 分鐘,這時(shí)候主庫 A 掉電了,HA 系統(tǒng)要切換 B 作為主庫。我們?cè)谥鲃?dòng)切換的時(shí)候,可以等到主備延遲小于 5 秒的時(shí)候再啟動(dòng)切換,但這時(shí)候已經(jīng)別無選擇了。

采用可靠性優(yōu)先策略的話,你就必須得等到備庫 B 的 seconds_behind_master=0 之后,才能切換。但現(xiàn)在的情況比剛剛更嚴(yán)重,并不是系統(tǒng)只讀、不可寫的問題了,而是系統(tǒng)處于完全不可用的狀態(tài)。因?yàn)椋鲙?A 掉電后,我們的連接還沒有切到備庫 B。
那能不能直接切換到備庫 B,但是保持 B 只讀呢?
這樣也不行。因?yàn)?,這段時(shí)間內(nèi),中轉(zhuǎn)日志還沒有應(yīng)用完成,如果直接發(fā)起主備切換,客戶端查詢看不到之前執(zhí)行完成的事務(wù),會(huì)認(rèn)為有“數(shù)據(jù)丟失”。雖然隨著中轉(zhuǎn)日志的繼續(xù)應(yīng)用,這些數(shù)據(jù)會(huì)恢復(fù)回來,但是對(duì)于一些業(yè)務(wù)來說,查詢到“暫時(shí)丟失數(shù)據(jù)的狀態(tài)”也是不能被接受的。


總結(jié)
今天介紹了,什么是主從延遲,主從延遲的五大來源,通過舉例介紹了什么是可靠性優(yōu)先策略,可用性優(yōu)先策略??捎眯詢?yōu)先策略的性能是大于可靠性優(yōu)先策略的。但是可靠性優(yōu)先策略的安全性一致性大于可用性優(yōu)先策略。
剩下的就是根據(jù)自己的業(yè)務(wù)需求,自己抉擇啦。通過本篇學(xué)習(xí)你是否掌握了如何減少主從延遲以及高可用手法呢?
