在上一篇文章中,我和你介紹了 binlog 的基本內容,在一個主備關係中,每一個備庫接收主庫的 binlog 並執行。mysql
正常狀況下,只要主庫執行更新生成的全部 binlog,均可以傳到備庫並被正確地執行,備庫就能達到跟主庫一致的狀態,這就是最終一致性。sql
可是,MySQL 要提供高可用能力,只有最終一致性是不夠的。爲何這麼說呢?今天我就着重和你分析一下。數據庫
這裏,我再放一次上一篇文章中講到的雙 M 結構的主備切換流程圖。bash
圖 1 MySQL 主備切換流程 -- 雙 M 結構網絡
主備切換多是一個主動運維動做,好比軟件升級、主庫所在機器按計劃下線等,也多是被動操做,好比主庫所在機器掉電。運維
接下來,咱們先一塊兒看看主動切換的場景。函數
在介紹主動切換流程的詳細步驟以前,我要先跟你說明一個概念,即「同步延遲」。與數據同步有關的時間點主要包括如下三個:oop
1. 主庫 A 執行完成一個事務,寫入 binlog,咱們把這個時刻記爲 T1;
2. 以後傳給備庫 B,咱們把備庫 B 接收完這個 binlog 的時刻記爲 T2;
3. 備庫 B 執行完成這個事務,咱們把這個時刻記爲 T3。性能
所謂主備延遲,就是同一個事務,在備庫執行完成的時間和主庫執行完成的時間之間的差值,也就是 T3-T1。spa
你能夠在備庫上執行 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,建議使用 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 slavestatus,採集 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;
@一大隻、@HuaMax 同窗提到了第一個復現方法;
@Jonh 同窗提到了 IGNORE_SERVER_IDS 這個解決方法;
@React 提到,若是主備設置不一樣的步長,備庫是否是能夠設置爲可讀寫。個人建議是,只要這個節點設計內就不會有業務直接在上面執行更新,就建議設置爲 readonly。