25 | MySQL是怎麼保證高可用的?

在上一篇文章中,我和你介紹了binlog的基本內容,在一個主備關係中,每一個備庫接收主庫的binlog並執行。mysql

正常狀況下,只要主庫執行更新生成的全部binlog,均可以傳到備庫並被正確地執行,備庫就能達到跟主庫一致的狀態,這就是最終一致性。sql

可是,MySQL要提供高可用能力,只有最終一致性是不夠的。爲何這麼說呢?今天我就着重和你分析一下。數據庫

這裏,我再放一次上一篇文章中講到的雙M結構的主備切換流程圖。網絡

圖 1 MySQL主備切換流程--雙M結構

主備延遲

主備切換多是一個主動運維動做,好比軟件升級、主庫所在機器按計劃下線等,也多是被動操做,好比主庫所在機器掉電。運維

接下來,咱們先一塊兒看看主動切換的場景。函數

在介紹主動切換流程的詳細步驟以前,我要先跟你說明一個概念,即「同步延遲」。與數據同步有關的時間點主要包括如下三個:oop

  1. 主庫A執行完成一個事務,寫入binlog,咱們把這個時刻記爲T1;性能

  2. 以後傳給備庫B,咱們把備庫B接收完這個binlog的時刻記爲T2;spa

  3. 備庫B執行完成這個事務,咱們把這個時刻記爲T3。線程

所謂主備延遲,就是同一個事務,在備庫執行完成的時間和主庫執行完成的時間之間的差值,也就是T3-T1。

你能夠在備庫上執行show slave status命令,它的返回結果裏面會顯示seconds_behind_master,用於表示當前備庫延遲了多少秒。

seconds_behind_master的計算方法是這樣的:

  1. 每一個事務的binlog 裏面都有一個時間字段,用於記錄主庫上寫入的時間;

  2. 備庫取出當前正在執行的事務的時間字段的值,計算它與當前系統時間的差值,獲得seconds_behind_master。

能夠看到,其實seconds_behind_master這個參數計算的就是T3-T1。因此,咱們能夠用seconds_behind_master來做爲主備延遲的值,這個值的時間精度是秒。

你可能會問,若是主備庫機器的系統時間設置不一致,會不會致使主備延遲的值不許?

其實不會的。由於,備庫鏈接到主庫的時候,會經過執行SELECT UNIX_TIMESTAMP()函數來得到當前主庫的系統時間。若是這時候發現主庫的系統時間與本身不一致,備庫在執行seconds_behind_master計算的時候會自動扣掉這個差值。

須要說明的是,在網絡正常的時候,日誌從主庫傳給備庫所需的時間是很短的,即T2-T1的值是很是小的。也就是說,網絡正常狀況下,主備延遲的主要來源是備庫接收完binlog和執行完這個事務之間的時間差。

因此說,主備延遲最直接的表現是,備庫消費中轉日誌(relay log)的速度,比主庫生產binlog的速度要慢。接下來,我就和你一塊兒分析下,這多是由哪些緣由致使的。

主備延遲的來源

首先,有些部署條件下,備庫所在機器的性能要比主庫所在的機器性能差。

通常狀況下,有人這麼部署時的想法是,反正備庫沒有請求,因此能夠用差一點兒的機器。或者,他們會把20個主庫放在4臺機器上,而把備庫集中在一臺機器上。

其實咱們都知道,更新請求對IOPS的壓力,在主庫和備庫上是無差異的。因此,作這種部署時,通常都會將備庫設置爲「非雙1」的模式。

但實際上,更新過程當中也會觸發大量的讀操做。因此,當備庫主機上的多個備庫都在爭搶資源的時候,就可能會致使主備延遲了。

固然,這種部署如今比較少了。由於主備可能發生切換,備庫隨時可能變成主庫,因此主備庫選用相同規格的機器,而且作對稱部署,是如今比較常見的狀況。

追問1:可是,作了對稱部署之後,還可能會有延遲。這是爲何呢?

這就是第二種常見的可能了,即備庫的壓力大。通常的想法是,主庫既然提供了寫能力,那麼備庫能夠提供一些讀能力。或者一些運營後臺須要的分析語句,不能影響正常業務,因此只能在備庫上跑。

我真就見過很多這樣的狀況。因爲主庫直接影響業務,你們使用起來會比較剋制,反而忽視了備庫的壓力控制。結果就是,備庫上的查詢耗費了大量的CPU資源,影響了同步速度,形成主備延遲。

這種狀況,咱們通常能夠這麼處理:

  1. 一主多從。除了備庫外,能夠多接幾個從庫,讓這些從庫來分擔讀的壓力。

  2. 經過binlog輸出到外部系統,好比Hadoop這類系統,讓外部系統提供統計類查詢的能力。

其中,一主多從的方式大都會被採用。由於做爲數據庫系統,還必須保證有按期全量備份的能力。而從庫,就很適合用來作備份。

備註:這裏須要說明一下,從庫和備庫在概念上其實差很少。在咱們這個專欄裏,爲了方便描述,我把會在HA過程當中被選成新主庫的,稱爲備庫,其餘的稱爲從庫。

追問2:採用了一主多從,保證備庫的壓力不會超過主庫,還有什麼狀況可能致使主備延遲嗎?

這就是第三種可能了,即大事務。

大事務這種狀況很好理解。由於主庫上必須等事務執行完成纔會寫入binlog,再傳給備庫。因此,若是一個主庫上的語句執行10分鐘,那這個事務極可能就會致使從庫延遲10分鐘。

不知道你所在公司的DBA有沒有跟你這麼說過:不要一次性地用delete語句刪除太多數據。其實,這就是一個典型的大事務場景。

好比,一些歸檔類的數據,平時沒有注意刪除歷史數據,等到空間快滿了,業務開發人員要一次性地刪掉大量歷史數據。同時,又由於要避免在高峯期操做會影響業務(至少有這個意識仍是很不錯的),因此會在晚上執行這些大量數據的刪除操做。

結果,負責的DBA同窗半夜就會收到延遲報警。而後,DBA團隊就要求你後續再刪除數據的時候,要控制每一個事務刪除的數據量,分紅屢次刪除。

另外一種典型的大事務場景,就是大表DDL。這個場景,我在前面的文章中介紹過。處理方案就是,計劃內的DDL,建議使用gh-ost方案(這裏,你能夠再回顧下第13篇文章《爲何表數據刪掉一半,表文件大小不變?》中的相關內容)。

追問3:若是主庫上也不作大事務了,還有什麼緣由會致使主備延遲嗎?

形成主備延遲還有一個大方向的緣由,就是備庫的並行複製能力。這個話題,我會留在下一篇文章再和你詳細介紹。

其實仍是有很多其餘狀況會致使主備延遲,若是你還碰到過其餘場景,歡迎你在評論區給我留言,我來和你一塊兒分析、討論。

因爲主備延遲的存在,因此在主備切換的時候,就相應的有不一樣的策略。

可靠性優先策略

在圖1的雙M結構下,從狀態1到狀態2切換的詳細過程是這樣的:

  1. 判斷備庫B如今的seconds_behind_master,若是小於某個值(好比5秒)繼續下一步,不然持續重試這一步;

  2. 把主庫A改爲只讀狀態,即把readonly設置爲true;

  3. 判斷備庫B的seconds_behind_master的值,直到這個值變成0爲止;

  4. 把備庫B改爲可讀寫狀態,也就是把readonly 設置爲false;

  5. 把業務請求切到備庫B。

這個切換流程,通常是由專門的HA系統來完成的,咱們暫時稱之爲可靠性優先流程。

圖2 MySQL可靠性優先主備切換流程

備註:圖中的SBM,是seconds_behind_master參數的簡寫。

能夠看到,這個切換流程中是有不可用時間的。由於在步驟2以後,主庫A和備庫B都處於readonly狀態,也就是說這時系統處於不可寫狀態,直到步驟5完成後才能恢復。

在這個不可用狀態中,比較耗費時間的是步驟3,可能須要耗費好幾秒的時間。這也是爲何須要在步驟1先作判斷,確保seconds_behind_master的值足夠小。

試想若是一開始主備延遲就長達30分鐘,而不先作判斷直接切換的話,系統的不可用時間就會長達30分鐘,這種狀況通常業務都是不可接受的。

固然,系統的不可用時間,是由這個數據可靠性優先的策略決定的。你也能夠選擇可用性優先的策略,來把這個不可用時間幾乎降爲0。

可用性優先策略

若是我強行把步驟四、5調整到最開始執行,也就是說不等主備數據同步,直接把鏈接切到備庫B,而且讓備庫B能夠讀寫,那麼系統幾乎就沒有不可用時間了。

咱們把這個切換流程,暫時稱做可用性優先流程。這個切換流程的代價,就是可能出現數據不一致的狀況。

接下來,我就和你分享一個可用性優先流程產生數據不一致的例子。假設有一個表 t:

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);

這個表定義了一個自增主鍵id,初始化數據後,主庫和備庫上都是3行數據。接下來,業務人員要繼續在表t上執行兩條插入語句的命令,依次是:

insert into t(c) values(4);
insert into t(c) values(5);

假設,如今主庫上其餘的數據表有大量的更新,致使主備延遲達到5秒。在插入一條c=4的語句後,發起了主備切換。

圖3是可用性優先策略,且binlog_format=mixed時的切換流程和數據結果。

圖3 可用性優先策略,且binlog_format=mixed

如今,咱們一塊兒分析下這個切換流程:

  1. 步驟2中,主庫A執行完insert語句,插入了一行數據(4,4),以後開始進行主備切換。

  2. 步驟3中,因爲主備之間有5秒的延遲,因此備庫B還沒來得及應用「插入c=4」這個中轉日誌,就開始接收客戶端「插入 c=5」的命令。

  3. 步驟4中,備庫B插入了一行數據(4,5),而且把這個binlog發給主庫A。

  4. 步驟5中,備庫B執行「插入c=4」這個中轉日誌,插入了一行數據(5,4)。而直接在備庫B執行的「插入c=5」這個語句,傳到主庫A,就插入了一行新數據(5,5)。

最後的結果就是,主庫A和備庫B上出現了兩行不一致的數據。能夠看到,這個數據不一致,是由可用性優先流程致使的。

那麼,若是我仍是用可用性優先策略,但設置binlog_format=row,狀況又會怎樣呢?

由於row格式在記錄binlog的時候,會記錄新插入的行的全部字段值,因此最後只會有一行不一致。並且,兩邊的主備同步的應用線程會報錯duplicate key error並中止。也就是說,這種狀況下,備庫B的(5,4)和主庫A的(5,5)這兩行數據,都不會被對方執行。

圖4中我畫出了詳細過程,你能夠本身再分析一下。

圖4 可用性優先策略,且binlog_format=row

從上面的分析中,你能夠看到一些結論:

  1. 使用row格式的binlog時,數據不一致的問題更容易被發現。而使用mixed或者statement格式的binlog時,數據極可能悄悄地就不一致了。若是你過了好久才發現數據不一致的問題,極可能這時的數據不一致已經不可查,或者連帶形成了更多的數據邏輯不一致。

  2. 主備切換的可用性優先策略會致使數據不一致。所以,大多數狀況下,我都建議你使用可靠性優先策略。畢竟對數據服務來講的話,數據的可靠性通常仍是要優於可用性的。

但事無絕對,有沒有哪一種狀況數據的可用性優先級更高呢?

答案是,有的。

我曾經碰到過這樣的一個場景:

  • 有一個庫的做用是記錄操做日誌。這時候,若是數據不一致能夠經過binlog來修補,而這個短暫的不一致也不會引起業務問題。
  • 同時,業務系統依賴於這個日誌寫入邏輯,若是這個庫不可寫,會致使線上的業務操做沒法執行。

這時候,你可能就須要選擇先強行切換,過後再補數據的策略。

固然,過後覆盤的時候,咱們想到了一個改進措施就是,讓業務邏輯不要依賴於這類日誌的寫入。也就是說,日誌寫入這個邏輯模塊應該能夠降級,好比寫到本地文件,或者寫到另一個臨時庫裏面。

這樣的話,這種場景就又可使用可靠性優先策略了。

接下來咱們再看看,按照可靠性優先的思路,異常切換會是什麼效果?

假設,主庫A和備庫B間的主備延遲是30分鐘,這時候主庫A掉電了,HA系統要切換B做爲主庫。咱們在主動切換的時候,能夠等到主備延遲小於5秒的時候再啓動切換,但這時候已經別無選擇了。

圖5 可靠性優先策略,主庫不可用

採用可靠性優先策略的話,你就必須得等到備庫B的seconds_behind_master=0以後,才能切換。但如今的狀況比剛剛更嚴重,並非系統只讀、不可寫的問題了,而是系統處於徹底不可用的狀態。由於,主庫A掉電後,咱們的鏈接尚未切到備庫B。

你可能會問,那能不能直接切換到備庫B,可是保持B只讀呢?

這樣也不行。

由於,這段時間內,中轉日誌尚未應用完成,若是直接發起主備切換,客戶端查詢看不到以前執行完成的事務,會認爲有「數據丟失」。

雖然隨着中轉日誌的繼續應用,這些數據會恢復回來,可是對於一些業務來講,查詢到「暫時丟失數據的狀態」也是不能被接受的。

聊到這裏你就知道了,在知足數據可靠性的前提下,MySQL高可用系統的可用性,是依賴於主備延遲的。延遲的時間越小,在主庫故障的時候,服務恢復須要的時間就越短,可用性就越高。

小結

今天這篇文章,我先和你介紹了MySQL高可用系統的基礎,就是主備切換邏輯。緊接着,我又和你討論了幾種會致使主備延遲的狀況,以及相應的改進方向。

而後,因爲主備延遲的存在,切換策略就有不一樣的選擇。因此,我又和你一塊兒分析了可靠性優先和可用性優先策略的區別。

在實際的應用中,我更建議使用可靠性優先的策略。畢竟保證數據準確,應該是數據庫服務的底線。在這個基礎上,經過減小主備延遲,提高系統的可用性。

最後,我給你留下一個思考題吧。

通常如今的數據庫運維繫統都有備庫延遲監控,其實就是在備庫上執行 show slave status,採集seconds_behind_master的值。

假設,如今你看到你維護的一個備庫,它的延遲監控的圖像相似圖6,是一個45°斜向上的線段,你以爲多是什麼緣由致使呢?你又會怎麼去確認這個緣由呢?

圖6 備庫延遲

你能夠把你的分析寫在評論區,我會在下一篇文章的末尾跟你討論這個問題。感謝你的收聽,也歡迎你把這篇文章分享給更多的朋友一塊兒閱讀。

上期問題時間

上期我留給你的問題是,什麼狀況下雙M結構會出現循環複製。

一種場景是,在一個主庫更新事務後,用命令set global server_id=x修改了server_id。等日誌再傳回來的時候,發現server_id跟本身的server_id不一樣,就只能執行了。

另外一種場景是,有三個節點的時候,如圖7所示,trx1是在節點 B執行的,所以binlog上的server_id就是B,binlog傳給節點 A,而後A和A’搭建了雙M結構,就會出現循環複製。

圖7 三節點循環複製

這種三節點複製的場景,作數據庫遷移的時候會出現。

若是出現了循環複製,能夠在A或者A’上,執行以下命令:

stop slave;
CHANGE MASTER TO IGNORE_SERVER_IDS=(server_id_of_B);
start slave;

這樣這個節點收到日誌後就不會再執行。過一段時間後,再執行下面的命令把這個值改回來。

stop slave;
CHANGE MASTER TO IGNORE_SERVER_IDS=();
start slave;
相關文章
相關標籤/搜索