從源碼分析 MGR 的新主選舉演算法

来源:https://www.cnblogs.com/ivictor/archive/2022/10/10/16776301.html
-Advertisement-
Play Games

MGR 的新主選舉演算法,在節點版本一致的情況下,其實也挺簡單的。 首先比較權重,權重越高,選為新主的優先順序越高。 如果權重一致,則會進一步比較節點的 server_uuid。server_uuid 越小,選為新主的優先順序越高。 所以,在節點版本一致的情況下,會選擇權重最高,server_uuid 最 ...


MGR 的新主選舉演算法,在節點版本一致的情況下,其實也挺簡單的。

首先比較權重,權重越高,選為新主的優先順序越高。

如果權重一致,則會進一步比較節點的 server_uuid。server_uuid 越小,選為新主的優先順序越高。

所以,在節點版本一致的情況下,會選擇權重最高,server_uuid 最小的節點作為新的主節點。

節點的權重由 group_replication_member_weight 決定,該參數是 MySQL 5.7.20 引入的,可設置 0 到 100 之間的任意整數值,預設是 50。

但如果集群節點版本不一致,實際的選舉演算法就沒這麼簡單了。

下麵,我們結合源碼具體分析下。

代碼實現邏輯

新主選舉演算法主要會涉及三個函數:

  1. pick_primary_member
  2. sort_and_get_lowest_version_member_position
  3. sort_members_for_election

這三個函數都是在 primary_election_invocation_handler.cc 中定義的。

其中,pick_primary_member 是主函數,它會基於其它兩個函數的結果選擇 Primary 節點。

下麵,我們從 pick_primary_member 出發,看看這三個函數的具體實現邏輯。


pick_primary_member

bool Primary_election_handler::pick_primary_member(
    std::string &primary_uuid,
    std::vector<Group_member_info *> *all_members_info) {
  DBUG_TRACE;

  bool am_i_leaving = true;
#ifndef NDEBUG
  int n = 0;
#endif
  Group_member_info *the_primary = nullptr;

  std::vector<Group_member_info *>::iterator it;
  std::vector<Group_member_info *>::iterator lowest_version_end;

  // 基於 member_version 選擇候選節點。
  lowest_version_end =
      sort_and_get_lowest_version_member_position(all_members_info);

  // 基於節點權重和 server_uuid 對候選節點進行排序。
  sort_members_for_election(all_members_info, lowest_version_end);


  // 遍歷所有節點,判斷 Primary 節點是否已定義。
  for (it = all_members_info->begin(); it != all_members_info->end(); it++) {
#ifndef NDEBUG
    assert(n <= 1);
#endif

    Group_member_info *member = *it;
    // 如果當前節點是單主模式且遍歷的節點中有 Primary 節點,則將該節點賦值給 the_primary
    if (local_member_info->in_primary_mode() && the_primary == nullptr &&
        member->get_role() == Group_member_info::MEMBER_ROLE_PRIMARY) {
      the_primary = member;
#ifndef NDEBUG
      n++;
#endif
    }

    // 檢查當前節點的狀態是否為 OFFLINE。
    if (!member->get_uuid().compare(local_member_info->get_uuid())) {
      am_i_leaving =
          member->get_recovery_status() == Group_member_info::MEMBER_OFFLINE;
    }
  }

  // 如果當前節點的狀態不是 OFFLINE 且 the_primary 還是為空,則選擇一個 Primary 節點
  if (!am_i_leaving) {
    if (the_primary == nullptr) {
      // 因為迴圈的結束條件是 it != lowest_version_end 且 the_primary 為空,所以基本上會將候選節點中的第一個節點作為 Primary 節點。
      for (it = all_members_info->begin();
           it != lowest_version_end && the_primary == nullptr; it++) {
        Group_member_info *member_info = *it;

        assert(member_info);
        if (member_info && member_info->get_recovery_status() ==
                               Group_member_info::MEMBER_ONLINE)
          the_primary = member_info;
      }
    }
  }

  if (the_primary == nullptr) return true;

  primary_uuid.assign(the_primary->get_uuid());
  return false;
}

這個函數裡面,比較關鍵的地方有三個:

  1. 調用 sort_and_get_lowest_version_member_position。

    這個函數會基於 member_version (節點版本)選擇候選節點。

    只有候選節點才有資格被選為主節點 。

  2. 調用 sort_members_for_election。

    這個函數會基於節點權重和 server_uuid,對候選節點進行排序。

  3. 基於排序後的候選節點選擇 Primary 節點。

    因為候選節點是從頭開始遍歷,所以基本上,只要第一個節點是 ONLINE 狀態,就會把這個節點作為 Primary 節點。


sort_and_get_lowest_version_member_position

接下來我們看看 sort_and_get_lowest_version_member_position 函數的實現邏輯。

sort_and_get_lowest_version_member_position(
    std::vector<Group_member_info *> *all_members_info) {
  std::vector<Group_member_info *>::iterator it;

  // 按照版本對 all_members_info 從小到大排序
  std::sort(all_members_info->begin(), all_members_info->end(),
            Group_member_info::comparator_group_member_version);

  // std::vector::end 會返回一個迭代器,該迭代器引用 vector (向量容器)中的末尾元素。
  // 註意,這個元素指向的是 vector 最後一個元素的下一個位置,不是最後一個元素。
  std::vector<Group_member_info *>::iterator lowest_version_end =
      all_members_info->end();

  // 獲取排序後的第一個節點,這個節點版本最低。
  it = all_members_info->begin();
  Group_member_info *first_member = *it;
  // 獲取第一個節點的 major_version
  // 對於 MySQL 5.7,major_version 是 5;對於 MySQL 8.0,major_version 是 8
  uint32 lowest_major_version =
      first_member->get_member_version().get_major_version();
  
  /* to avoid read compatibility issue leader should be picked only from lowest
     version members so save position where member version differs.
     From 8.0.17 patch version will be considered during version comparison.

     set lowest_version_end when major version changes

     eg: for a list: 5.7.18, 5.7.18, 5.7.19, 5.7.20, 5.7.21, 8.0.2
         the members to be considered for election will be:
            5.7.18, 5.7.18, 5.7.19, 5.7.20, 5.7.21
         and server_uuid based algorithm will be used to elect primary

     eg: for a list: 5.7.20, 5.7.21, 8.0.2, 8.0.2
         the members to be considered for election will be:
            5.7.20, 5.7.21
         and member weight based algorithm will be used to elect primary

     eg: for a list: 8.0.17, 8.0.18, 8.0.19
         the members to be considered for election will be:
            8.0.17

     eg: for a list: 8.0.13, 8.0.17, 8.0.18
         the members to be considered for election will be:
            8.0.13, 8.0.17, 8.0.18
         and member weight based algorithm will be used to elect primary
  */
  

  // 遍歷剩下的節點,註意 it 是從 all_members_info->begin() + 1 開始的
  for (it = all_members_info->begin() + 1; it != all_members_info->end();
       it++) {
   // 如果第一個節點的版本號大於 MySQL 8.0.17,且節點的版本號不等於第一個節點的版本號,則將該節點賦值給 lowest_version_end,並退出迴圈。
    if (first_member->get_member_version() >=
            PRIMARY_ELECTION_PATCH_CONSIDERATION &&
        (first_member->get_member_version() != (*it)->get_member_version())) {
      lowest_version_end = it;
      break;
    }
    // 如果節點的 major_version 不等於第一個節點的 major_version,則將該節點賦值給 lowest_version_end,並退出迴圈。
    if (lowest_major_version !=
        (*it)->get_member_version().get_major_version()) {
      lowest_version_end = it;
      break;
    }
  }
  return lowest_version_end;
}

函數中的 PRIMARY_ELECTION_PATCH_CONSIDERATION 是 0x080017,即 MySQL 8.0.17。

在 MySQL 8.0.17 中,Group Replication 引入了相容性策略。引入相容性策略的初衷是為了避免集群中出現節點不相容的情況。

該函數首先會對 all_members_info 按照版本從小到大排序。

接著會基於第一個節點的版本(最小版本)確定 lowest_version_end。

MGR 用 lowest_version_end 標記最低版本的結束點。只有 lowest_version_end 之前的節點才是候選節點。

lowest_version_end 的取值邏輯如下:

  1. 如果最小版本大於等於 MySQL 8.0.17,則會將最小版本之後的第一個節點設置為 lowest_version_end。
  2. 如果集群中既有 5.7,又有 8.0,則會將 8.0 的第一個節點設置為  lowest_version_end。
  3. 如果最小版本小於  MySQL 8.0.17,且只有一個大版本(major_version),則會取 all_members_info->end()。此時,所有節點都是候選節點。

為了方便大家理解代碼的邏輯,函數註釋部分還列舉了四個案例,每個案例對應一個典型場景。後面我們會具體分析下。


sort_members_for_election

最後,我們看看 sort_members_for_election 函數的實現邏輯。

void sort_members_for_election(
    std::vector<Group_member_info *> *all_members_info,
    std::vector<Group_member_info *>::iterator lowest_version_end) {
  Group_member_info *first_member = *(all_members_info->begin());
  // 獲取第一個節點的版本,這個節點版本最低。
  Member_version lowest_version = first_member->get_member_version();

  // 如果最小版本大於等於 MySQL 5.7.20,則根據節點的權重來排序。權重越高,在 vector 中的位置越靠前。
  // 註意,這裡只會對 [all_members_info->begin(), lowest_version_end) 這個區間內的元素進行排序,不包括 lowest_version_end。
  if (lowest_version >= PRIMARY_ELECTION_MEMBER_WEIGHT_VERSION)
    std::sort(all_members_info->begin(), lowest_version_end,
              Group_member_info::comparator_group_member_weight);
  else
   // 如果最小版本小於 MySQL 5.7.20,則根據節點的 server_uuid 來排序。server_uuid 越小,在 vector 中的位置越靠前。
    std::sort(all_members_info->begin(), lowest_version_end,
              Group_member_info::comparator_group_member_uuid);
}

函數中的 PRIMARY_ELECTION_MEMBER_WEIGHT_VERSION 是 0x050720,即 MySQL 5.7.20。

如果最小節點的版本大於等於 MySQL 5.7.20,則會基於權重來排序。權重越高,在 all_members_info 中的位置越靠前。

如果最小節點的版本小於 MySQL 5.7.20,則會基於節點的 server_uuid 來排序。server_uuid 越小,在 all_members_info 中的位置越靠前。

註意,std::sort 中的結束位置是 lowest_version_end,所以 lowest_version_end 這個節點不會參與排序。


comparator_group_member_weight

在基於權重進行排序時,如果兩個節點的權重一致,還會進一步比較這兩個節點的 server_uuid。

這個邏輯是在 comparator_group_member_weight 中定義的。

權重一致,節點的 server_uuid 越小,在 all_members_info 中的位置越靠前。

bool Group_member_info::comparator_group_member_weight(Group_member_info *m1,
                                                       Group_member_info *m2) {
  return m1->has_greater_weight(m2);
}

bool Group_member_info::has_greater_weight(Group_member_info *other) {
  MUTEX_LOCK(lock, &update_lock);
  if (member_weight > other->get_member_weight()) return true;
  // 如果權重一致,會按照節點的 server_uuid 來排序。
  if (member_weight == other->get_member_weight())
    return has_lower_uuid_internal(other);

  return false;
}

 

案例分析

基於上面代碼的邏輯,接下來我們分析下 sort_and_get_lowest_version_member_position 函數註釋部分列舉的四個案例:

案例 1:5.7.18, 5.7.18, 5.7.19, 5.7.20, 5.7.21, 8.0.2

1. 這幾個節點中,最小版本號是 5.7.18,小於 MySQL 8.0.17。所以會比較各個節點的 major_version,因為最後一個節點(8.0.2)的 major_version 和第一個節點不一致,所以會將 8.0.2 作為 lowest_version_end。此時,除了 8.0.2,其它都是候選節點。

2. 最小版本號 5.7.18 小於 MySQL 5.7.20,所以 5.7.18, 5.7.18, 5.7.19, 5.7.20, 5.7.21 這幾個節點會根據 server_uuid 進行排序。註意,lowest_version_end 的節點不會參與排序。

3. 選擇 server_uuid 最小的節點作為 Primary 節點。

 

案例 2:5.7.20, 5.7.21, 8.0.2, 8.0.2

1. 同案例 1 一樣,會將 8.0.2 作為 lowest_version_end。此時,候選節點只有 5.7.20 和 5.7.21。

2. 最小版本號 5.7.20 等於 MySQL 5.7.20,所以,5.7.20, 5.7.21 這兩個節點會根據節點的權重進行排序。如果權重一致,則會基於 server_uuid 進行進一步的排序。

3. 選擇權重最高,server_uuid 最小的節點作為 Primary 節點。

 

案例 3:8.0.17, 8.0.18, 8.0.19

1. 最小版本號是 MySQL 8.0.17,等於 MySQL 8.0.17,所以會判斷其它節點的版本號是否與第一個節點相同。不相同,則會將該節點的版本號賦值給 lowest_version_end。所以,會將 8.0.18 作為 lowest_version_end。此時,候選節點只有 8.0.17。

2. 選擇 8.0.17 這個節點作為 Primary 節點。

 

案例 4:8.0.13, 8.0.17, 8.0.18

1. 最小版本號是 MySQL 8.0.13,小於 MySQL 8.0.17,而且各個節點的 major_version 一致,所以最後返回的 lowest_version_end 實際上是 all_members_info->end()。此時,這三個節點都是候選節點。

2. MySQL 8.0.13 大於 MySQL 5.7.20,所以這三個節點會根據權重進行排序。如果權重一致,則會基於 server_uuid 進行進一步的排序。

3. 選擇權重最高,server_uuid 最小的節點作為 Primary 節點。

 

手動選主

從 MySQL 8.0.13 開始,我們可以通過以下兩個函數手動選擇新的主節點:

  • group_replication_set_as_primary(server_uuid) :切換單主模式下的 Primary 節點。
  • group_replication_switch_to_single_primary_mode([server_uuid]) :將多主模式切換為單主模式。可通過 server_uuid 指定單主模式下的 Primary 節點。

在使用這兩個參數時,註意,指定的 server_uuid 必須屬於候選節點。

另外,這兩個函數是 MySQL 8.0.13 引入的,所以,如果集群中存在 MySQL 8.0.13 之前的節點,執行時會報錯。

mysql> select group_replication_set_as_primary('5470a304-3bfa-11ed-8bee-83f233272a5d');
ERROR 3910 (HY000): The function 'group_replication_set_as_primary' failed. The group has a member with a version that does not support group coordinated operations.

 

總結

結合代碼和上面四個案例的分析,最後我們總結下 MGR 的新主選舉演算法:

1. 如果集群中存在 MySQL 5.7 的節點,則會將 MySQL 5.7 的節點作為候選節點。

2. 如果集群節點的版本都是 MySQL 8.0,這裡需要區分兩種情況:

  • 如果最小版本小於 MySQL 8.0.17,則所有的節點都可作為候選節點。
  • 如果最小版本大於等於 MySQL 8.0.17,則只有最小版本的節點會作為候選節點。

3. 在候選節點的基礎上,會進一步根據候選節點的權重和 server_uuid 選擇 Primary 節點。具體來說,

  • 如果候選節點中存在 MySQL 5.7.20 之前版本的節點,則會選擇 server_uuid 最小的節點作為 Primary 節點。
  • 如果候選節點都大於等於 MySQL 5.7.20,則會選擇權重最高,server_uuid 最小的節點作為 Primary 節點。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一:背景 1.講故事 最近分享了好幾篇關於 非托管記憶體泄漏 的文章,有時候就是這麼神奇,來求助的都是這類型的dump,一飲一啄,莫非前定。讓我被迫加深對 NT堆, 頁堆 的理解,這一篇就給大家再帶來一篇記憶體泄漏。 前段時間有位朋友找到我,說他的程式出現了非托管泄漏,某一塊的操作會導致非托管記憶體上漲的 ...
  • ​ 本周MASA Framework 進行了第四次課程直播,課程主題為類目管理的開發,直播中進行了理論講解和實戰演練(CQRS實踐的演示可直達推文底部觀看直播回放) 開始環節我們圍繞三個點介紹CQRS的原理 首先,我們先對之前的事件流概念進行簡單的回顧 事件風暴回顧 - 事件流 接下來到我們本節課的 ...
  • Linux目錄與路徑 1.絕對路徑與相對路徑 ​ 與Windows一樣,linux也有絕對路徑與相對路徑的區別: ​ Windows的絕對路徑是以某個盤為起點,如win的桌面的絕對路徑為: cd C:\Users\Administrator\Desktop ​ 而win的相對路徑,如桌面上有兩個文件 ...
  • Mac哪款觸控板手勢增強軟體好用呢?Multitouch mac是Mac平臺上一款mac觸控板手勢增強軟體,Multitouch mac版添加了各種不同的觸控板手勢,手指輕點、輕掃等就能快速進行操作,使用非常便捷。 詳情:Multitouch for Mac(觸控板手勢增強軟體) 簡單介紹 Mult ...
  • Mac上哪款策略游戲好玩?鋼鐵戰隊Iron Marines for mac推薦給大家,這是一款Mac平臺上好玩的策略游戲,該游戲的創作者同《王國保衛戰》三部曲相同,又是一款超凡的太空奇幻歷險之作。玩家將在游戲中指導你的部隊參加3個不同世界的21次任務,準備好發動英勇的攻擊,堅守陣地,拯救平民,潛入超 ...
  • 視圖和用戶許可權 1.視圖(view) 看一個需求 emp表的列信息很多,有些信息是個人重要信息(比如:sal、comm、mgr、hiredate),如果我們希望某個用戶只能查詢emp表的empno、enamel、job和deptno信息,有什麼辦法呢? 答案是使用視圖。 1.1基本原理 視圖是一個虛 ...
  • 我們偶爾需要在已有表,並且有數據的情況下,修改其某個欄位的類型或改變他的長度,但是因為表中有數據,所以不可以直接修改,需要換個思路。 -- Create table create table TABLE1 ( col1 number(9), col2 char(20) ); -- 嘗試修改 -- 修 ...
  • 在 StoneDB 1.0 版本中,InnoDB 引擎處理 OLTP 的事務型業務,Tianmu 引擎處理 OLAP 的分析型業務。因此,需要在主從複製環境的基礎上做讀寫分離,所有的寫操作和部分讀操作走 InnoDB 引擎,所有的分析類查詢走 Tianmu 引擎。讀寫分離方案既可以使用第三方中間件, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...