MySQL高可用,就這麼完美???

語言: CN / TW / HK

theme: channing-cyan

小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。

MySQL以其容易學習和高可用,被開發人員青睞。它的幾乎所有的高可用架構,都直接依賴於 binlog。MySQL 能夠成為現下最流行的開源資料庫,binlog 功不可沒。MySQL是怎樣實現高可用的?這種高可用足夠完美嗎?

主備同步流程

流程

主庫為A,備庫為B,其同步流程如下圖所示,這張圖也很好的闡明一條更新語句,在master會執行哪些動作:

圖片

備庫 B 跟主庫 A 之間維持了一個長連線。主庫 A 內部有一個執行緒,專門用於服務備庫 B 的這個長連線。一個事務日誌同步的完整過程是這樣的:

  1. 在備庫 B 上通過 change master 命令,設定主庫 A 的 IP、埠、使用者名稱、密碼,以及要從哪個位置開始請求 binlog,這個位置包含檔名和日誌偏移量。

  2. 在備庫 B 上執行 start slave 命令,這時候備庫會啟動兩個執行緒,就是圖中的 io_thread和 sql_thread。其中 io_thread 負責與主庫建立連線。

  3. 主庫 A 校驗完使用者名稱、密碼後,開始按照備庫 B 傳過來的位置,從本地讀取 binlog,發給 B。

  4. 備庫 B 拿到 binlog 後,寫到本地檔案,稱為中轉日誌(relay log)。

  5. sql_thread 讀取中轉日誌,解析出日誌裡的命令,並執行。

同步位置

主備切換後,從庫需要從新的主庫同步資料。即上面流程第一步,需要指定從哪個位置開始請求binlog。主要有兩種方案:

基於位點

MySQL5.6之前,使用change master命令更換主庫。

``` CHANGE MASTER TO

MASTER_HOST=$host_name MASTER_PORT=$port MASTER_USER=$user_name MASTER_PASSWORD=$password MASTER_LOG_FILE=$master_log_name MASTER_LOG_POS=$master_log_pos

```

操作流程如下:

  1. 等待新主庫 A’把中轉日誌(relay log)全部同步完成;

  2. 在 A’上執行 show master status 命令,得到當前 A’上最新的 File 和 Position;

  3. 取原主庫 A 故障的時刻 T;

  4. 用 mysqlbinlog 工具解析 A’的 File,得到 T 時刻的位點。

基於GTID

基於位點的方案太過繁瑣,MySQL 5.6 版本引入了 GTID,無需人工計算位點。

GTID 的全稱是 Global Transaction Identifier,也就是全域性事務 ID,是一個事務在提交的時候生成的,是這個事務的唯一標識。每個 MySQL 例項都維護了一個 GTID 集合,用來對應“這個例項執行過的所有事務”。

``` CHANGE MASTER TO MASTER_HOST=$host_name MASTER_PORT=$port MASTER_USER=$user_name MASTER_PASSWORD=$password master_auto_position=1

```

master_auto_position=1 就表示這個主備關係使用的是 GTID 協議。

在例項 B 上執行 start slave 命令,取 binlog 的邏輯如下所示,其中set_a和set_b為執行過的事務的 GTID 集合:

  1. 例項 B 指定主庫 A’,基於主備協議建立連線。

  2. 例項 B 把 set_b 發給主庫 A’。

  3. 例項 A’算出 set_a 與 set_b 的差集,也就是所有存在於 set_a,但是不存在於 set_b的 GTID 的集合,判斷 A’本地是否包含了這個差集需要的所有 binlog 事務。

    a. 如果不包含,表示 A’已經把例項 B 需要的 binlog 給刪掉了,直接返回錯誤;

    b. 如果確認全部包含,A’從自己的 binlog 檔案裡面,找出第一個不在 set_b 的事務,發給 B;

  4. 之後就從這個事務開始,往後讀檔案,按順序取 binlog 發給 B 去執行。

基於GTID的操作,可以認為是系統自行計算出對應位點。

迴圈問題

引數 log_slave_updates 設定為 on,表示備庫執行 relay log 後生成 binlog。在主主複製+主從複製情況下,有時會發現主從沒有同步,很可能是因為有的主庫沒有將log_slave_updates設定為on。

既然消費relay log會生成新的binlog,那雙master情況下為何沒有產生節點間迴圈複製情況?

主要是因為MySQL 在 binlog 中記錄了這個命令第一次執行時所在例項的server id。

  1. 規定兩個庫的 server id 必須不同,如果相同,則它們之間不能設定為主備關係;

  2. 一個備庫接到 binlog 並在重放的過程中,生成與原 binlog 的 server id 相同的新的binlog;

  3. 每個庫在收到從自己的主庫發過來的日誌後,先判斷 server id,如果跟自己的相同,表示這個日誌是自己生成的,就直接丟棄這個日誌。

高可用(HA)

現在大家都用MySQL,主要是因其高可用。高可用原因有兩個,一個是主備一致,一個是主備切換。這兩者缺一不可。

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

  2. 主庫出現問題,可以將備庫作為主庫,繼續提供服務。

MySQL 高可用系統的基礎,就是主備切換邏輯,但主備切換又很依賴主備延遲。

原因很容易理解,如果備庫同步沒有完成,此時將備庫更改為主庫,會產生資料丟失、資料不一致問題。

同步延遲

根據上面提到的主備同步流程,我們能夠看出與資料同步有關的時間點主要包括以下三個:

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

  2. 之後傳給備庫 B,我們把備庫 B 接收完這個 binlog 的時刻記為 T2;

  3. 備庫 B 執行完成這個事務,我們把這個時刻記為 T3。

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

延遲時間可通過在備庫上執行 show slave status 命令檢視,它的返回結果裡會顯示seconds_behind_master,用於表示當前備庫延遲了多少秒。DB監控上顯示的時間,就是seconds_behind_master。

圖片

延遲原因

主備延遲必然會有,但不應該延遲太長。主備延遲長,一般有如下幾個原因:

  1. 備庫所在機器的效能要比主庫所在的機器效能差

  2. 備庫的機器配置本身就比主庫差

  3. 主庫多機器部署,備庫單機部署

  4. 備庫的壓力大

  5. 備庫上的查詢耗費了大量的 CPU 資源

  6. 主庫執行大事務或大表 DDL

  7. 語句在主庫執行多久,便會導致從庫延遲多久

  8. 備庫的並行複製能力

  9. 備庫使用單執行緒複製還是多執行緒複製

  10. 從MySQL5.7.22開始,可以通過 binlog-transaction dependency-tracking 引數的 COMMIT_ORDER、WRITESET 和 WRITE_SESSION,選擇使用哪種並行複製策略

主備切換

下面是雙Master之間主備切換流程。一般說雙M是指AB之間設定為互為主備,不過任何時刻只有一個節點在接受更新。

主備切換主要有兩種策略,可靠性優先和可用性優先策略。

圖片

可靠性優先

從狀態 1 到狀態 2 切換的詳細過程是這樣的:

  1. 判斷備庫 B 現在的 seconds_behind_master,如果小於某個值(比如 5 秒)繼續下一步,否則持續重試;

  2. 把主庫 A 改成只讀狀態,即把 readonly 設定為 true;

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

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

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

可靠性優先的好處是,主備的資料完全一致後再進行切換,不會引起系統問題。但缺點是系統有段時間不可用。

可用性優先

從狀態 1 到狀態 2 切換的詳細過程是這樣的:

  1. 把備庫 B 改成可讀寫狀態,也就是把 readonly 設定為 false;

  2. 把業務請求切到備庫 B;

  3. 把主庫 A 改成只讀狀態,即把 readonly 設定為 true;

可用性優先的好處是,系統幾乎就沒有不可用時間,壞處是系統可能出現數據不一致情況。

之所以出現數據不一致,是因為備庫B接收業務請求的同時,還會繼續消費未完成的binlog日誌,新的請求和老的請求之間可能存在衝突。將binlog設定為row能夠更加及時的發現這種問題,減少問題的加劇。

異常情況

假設,主庫 A 和備庫 B 間的主備延遲是 30 分鐘,這時候主庫 A 掉電了,HA 系統要切換B 作為主庫。這時候切和不切都會有問題。

  • 切:有些資料在備庫無法查到,而且會產生資料不一致問題

  • 不切:資料庫不可使用

這也是為什麼說MySQL 高可用系統的可用性,是依賴於主備延遲的。延遲的時間越小,在主庫故障的時候,服務恢復需要的時間就越短,可用性就越高。所以無論是對DBA還是對研發人員而言,需要重點關注同步延遲。

問題

主從同步雖然給MySQL帶來了高可用,但因為必然存在延遲問題,所以會導致更新完主庫後,立即查從庫,此時從庫並沒有更新後的資料。這個問題無法避免,但可以想辦法優化,需要大家在付出和收益間做好權衡。

強制走主庫方案

  • 查詢操作不查從庫,查主庫

sleep 方案

  • 客戶端更新成功後,過一小會再做查詢操作

判斷主備無延遲方案

  • 每次從庫執行查詢請求前,先判斷seconds_behind_master 是否已經等於 0。如果還不等於 0 ,那就必須等到這個引數變為0 才能執行查詢請求。

  • 判斷位點:讀到的主庫的最新位點與備庫執行的最新位點做比較,相等即可讀

  • 判斷GTID:備庫收到的所有日誌的 GTID 集合與備庫所有已經執行完成的 GTID 集合是否一致,一致即可讀

配合 semi-sync 方案

  • 一主一備下,半同步複製能確保主備全都收到了更新

等主庫位點方案

  • 從庫上執行select master_pos_wait(file, pos[, timeout]), 返回值是 >=0 的正整數則查詢,其中引數 file 和 pos 指的是主庫上的檔名和位置

等 GTID 方案

  • 從庫上執行select wait_for_executed_gtid_set(gtid_set, 1),如果返回值是 0,則在這個從庫執行查詢語句,其中gtid_set是主庫事務更新完成後,從返回包直接獲取到事務的 GTID

總結

其實MySQL主從同步和開發人員相關性不大,但瞭解其中的不完美,對於發生異常時進行問題追查是很有幫助的。而且,能夠擴展出許多新的玩法,如業務消費binlog日誌,實現很多功能。

資料

  1. 主從同步設定的重要引數log_slave_updates

  2. MySQL45講

  3. MySQL DDL--ghost工具學習

最後

大家如果喜歡我的文章,可以關注我的公眾號(程式設計師麻辣燙)

我的個人部落格為:http://shidawuhen.github.io/

往期文章回顧:

  1. 設計模式

  2. 招聘

  3. 思考

  4. 儲存

  5. 算法系列

  6. 讀書筆記

  7. 小工具

  8. 架構

  9. 網路

  10. Go語言