讓 MGR 不從 Primary 的節點克隆數據?

問題

MGR 中,新節點在加入時,為了與組內其它節點的數據保持一致,它會首先經歷一個分布式恢復階段。在這個階段,新節點會隨機選擇組內一個節點(Donor)來同步差異數據。

在 MySQL 8.0.17 之前,同步的方式只有一種,即基于 Binlog 的異步復制,這種方式適用于差異數據較少或需要的 Binlog 都存在的場景。

從 MySQL 8.0.17 開始,新增了一種同步方式-克隆插件,克隆插件可用來進行物理備份恢復,這種方式適用于差異數據較多或需要的 Binlog 已被 purge 的場景。

克隆插件雖然極大提升了恢復的效率,但備份畢竟是一個 IO 密集型的操作,很容易影響備份實例的性能,所以,我們一般不希望克隆操作在 Primary 節點上執行。

但 Donor 的選擇是隨機的(后面會證明這一點),有沒有辦法讓 MGR 不從 Primary 節點克隆數據呢?

本文主要包括以下幾部分:

  1. MGR 是如何執行克隆操作的?
  2. 可以通過 clone_valid_donor_list 設置 Donor 么?
  3. MGR 是如何選擇 Donor 的?
  4. MGR 克隆操作的實現邏輯。
  5. group_replication_advertise_recovery_endpoints 的生效時機。

MGR 是如何執行克隆操作的?

起初還以為 MGR 執行克隆操作是調用克隆插件的一些內部接口。但實際上,MGR 調用的就是CLONE INSTANCE命令。

//?plugin/group_replication/src/sql_service/sql_service_command.cc
long?Sql_service_commands::internal_clone_server(Sql_service_interface?*sql_interface,?void?*var_args)?{...std::string?query?=?"CLONE?INSTANCE?FROM?\'";query.append(q_user);query.append("\'@\'");query.append(q_hostname);query.append("\':");query.append(std::get<1>(*variable_args));query.append("?IDENTIFIED?BY?\'");query.append(q_password);bool?use_ssl?=?std::get<4>(*variable_args);if?(use_ssl)query.append("\'?REQUIRE?SSL;");elsequery.append("\'?REQUIRE?NO?SSL;");Sql_resultset?rset;long?srv_err?=?sql_interface->execute_query(query,?&rset);...
}

既然調用的是 CLONE INSTANCE 命令,那是不是就可以通過 clone_valid_donor_list 參數來設置 Donor(被克隆實例)呢?

可以通過 clone_valid_donor_list 設置Donor么

不能。

在獲取到 Donor 的 endpoint(端點,由 hostname 和 port 組成)后,MGR 會通過update_donor_list函數設置 clone_valid_donor_list。

clone_valid_donor_list 的值即為 Donor 的 endpoint。

所以,在啟動組復制之前,在 mysql 客戶端中顯式設置 clone_valid_donor_list 是沒有效果的。

//?plugin/group_replication/src/plugin_handlers/remote_clone_handler.cc
int?Remote_clone_handler::update_donor_list(Sql_service_command_interface?*sql_command_interface,?std::string?&hostname,std::string?&port)?{std::string?donor_list_query?=?"?SET?GLOBAL?clone_valid_donor_list?=?\'";plugin_escape_string(hostname);donor_list_query.append(hostname);donor_list_query.append(":");donor_list_query.append(port);donor_list_query.append("\'");std::string?error_msg;if?(sql_command_interface->execute_query(donor_list_query,?error_msg))?{...}return?0;
}

既然是先有 Donor,然后才會設置 clone_valid_donor_list,接下來我們看看 MGR 是如何選擇 Donor 的?

MGR 是如何選擇 Donor 的?

MGR 選擇 Donor 可分為以下兩步:

  1. 首先,判斷哪些節點適合當 Donor。滿足條件的節點會放到一個動態數組(m_suitable_donors)中, 這個操作是在Remote_clone_handler::get_clone_donors函數中實現的。
  2. 其次,循環遍歷 m_suitable_donors 中的節點作為 Donor。如果第一個節點執行克隆操作失敗,則會選擇第二個節點,依次類推。

下面,我們看看Remote_clone_handler::get_clone_donors的實現細節。

void?Remote_clone_handler::get_clone_donors(std::list<Group_member_info?*>?&suitable_donors)?{//?獲取集群所有節點的信息Group_member_info_list?*all_members_info?=group_member_mgr->get_all_members();if?(all_members_info->size()?>?1)?{//?這里將原來的 all_members_info 打亂了,從這里可以看到 donor 是隨機選擇的。vector_random_shuffle(all_members_info);}for?(Group_member_info?*member?:?*all_members_info)?{std::string?m_uuid?=?member->get_uuid();bool?is_online?=member->get_recovery_status()?==?Group_member_info::MEMBER_ONLINE;bool?not_self?=?m_uuid.compare(local_member_info->get_uuid());//?注意,這里只是比較了版本bool?supports_clone?=member->get_member_version().get_version()?>=CLONE_GR_SUPPORT_VERSION?&&member->get_member_version().get_version()?==local_member_info->get_member_version().get_version();if?(is_online?&&?not_self?&&?supports_clone)?{suitable_donors.push_back(member);}?else?{delete?member;}}delete?all_members_info;
}

該函數的處理流程如下:

  1. 獲取集群所有節點的信息,存儲到 all_members_info 中。

    all_members_info 是個動態數組,數組中的元素是按照節點 server_uuid 從小到大的順序依次存儲的。

  2. 通過vector_random_shuffle函數將 all_members_info 進行隨機重排。

  3. 選擇 ONLINE 狀態且版本大于等于 8.0.17 的節點添加到 suitable_donors 中。

    為什么是 8.0.17 呢,因為克隆插件是 MySQL 8.0.17 引入的。

    注意,這里只是比較了版本,沒有判斷克隆插件是否真正加載。

函數中的 suitable_donors 實際上就是 m_suitable_donors。

get_clone_donors(m_suitable_donors);

基于前面的分析,可以看到,在 MGR 中,作為被克隆節點的 Donor 是隨機選擇的。

既然 Donor 的選擇是隨機的,想不從 Primary 節點克隆數據似乎是實現不了的。

分析到這里,問題似乎是無解了。

別急,接下來讓我們分析下 MGR 克隆操作的實現邏輯。

MGR 克隆操作的實現邏輯

MGR 克隆操作是在Remote_clone_handler::clone_thread_handle函數中實現的。

//?plugin/group_replication/src/plugin_handlers/remote_clone_handler.cc
[[noreturn]]?void?Remote_clone_handler::clone_thread_handle()?{...while?(!empty_donor_list?&&?!m_being_terminated)?{stage_handler.set_completed_work(number_attempts);number_attempts++;std::string?hostname("");std::string?port("");std::vector<std::pair<std::string,?uint>>?endpoints;mysql_mutex_lock(&m_donor_list_lock);//?m_suitable_donors?是所有符合?Donor?條件的節點empty_donor_list?=?m_suitable_donors.empty();if?(!empty_donor_list)?{//?獲取數組中的第一個元素Group_member_info?*member?=?m_suitable_donors.front();Donor_recovery_endpoints?donor_endpoints;//?獲取?Donor?的端點信息?endpoints?=?donor_endpoints.get_endpoints(member);...//?從數組中移除第一個元素m_suitable_donors.pop_front();delete?member;empty_donor_list?=?m_suitable_donors.empty();number_servers?=?m_suitable_donors.size();}mysql_mutex_unlock(&m_donor_list_lock);//?No?valid?donor?in?the?listif?(endpoints.size()?==?0)?{error?=?1;continue;}//?循環遍歷?endpoints?中的每個端點for?(auto?endpoint?:?endpoints)?{hostname.assign(endpoint.first);port.assign(std::to_string(endpoint.second));//?設置?clone_valid_donor_listif?((error?=?update_donor_list(sql_command_interface,?hostname,?port)))?{continue;?/*?purecov:?inspected?*/}if?(m_being_terminated)?goto?thd_end;terminate_wait_on_start_process(WAIT_ON_START_PROCESS_ABORT_ON_CLONE);//?執行克隆操作error?=?run_clone_query(sql_command_interface,?hostname,?port,?username,password,?use_ssl);//?Even?on?critical?errors?we?continue?as?another?clone?can?fix?the?issueif?(!critical_error)?critical_error?=?evaluate_error_code(error);//?On?ER_RESTART_SERVER_FAILED?it?makes?no?sense?to?retryif?(error?==?ER_RESTART_SERVER_FAILED)?goto?thd_end;if?(error?&&?!m_being_terminated)?{if?(evaluate_server_connection(sql_command_interface))?{critical_error?=?true;goto?thd_end;}if?(group_member_mgr->get_number_of_members()?==?1)?{critical_error?=?true;goto?thd_end;}}//?如果失敗,則選擇下一個端點進行重試。if?(!error)?break;}//?如果失敗,則選擇下一個 Donor 進行重試。if?(!error)?break;}
...
}

該函數的處理流程如下:

  1. 首先會選擇一個 Donor。可以看到,代碼中是通過front()函數來獲取 m_suitable_donors 中的第一個元素。
  2. 獲取 Donor 的端點信息。
  3. 循環遍歷 endpoints 中的每個端點。
  4. 設置 clone_valid_donor_list。
  5. 執行克隆操作。如果操作失敗,則會進行重試,首先是選擇下一個端點進行重試。如果所有端點都遍歷完了,還是沒有成功,則會選擇下一個 Donor 進行重試,直到遍歷完所有 Donor。

當然,重試是有條件的,出現以下情況就不會進行重試:

  1. error == ER_RESTART_SERVER_FAILED:實例重啟失敗。

    實例重啟是克隆操作的最后一步,之前的步驟依次是:1. 獲取備份鎖。2. DROP 用戶表空間。3. 從 Donor 實例拷貝數據。

    既然數據都已經拷貝完了,就沒有必要進行重試了。

  2. 執行克隆操作的連接被 KILL 了且重建失敗。

  3. group_member_mgr->get_number_of_members() == 1:集群只有一個節點。

既然克隆操作失敗了會進行重試,那么思路來了,如果不想克隆操作在 Primary 節點上執行,很簡單,讓 Primary 節點上的克隆操作失敗了就行。

怎么讓它失敗呢?

一個克隆操作,如果要在 Donor(被克隆節點)上成功執行,Donor 需滿足以下條件:

  1. 安裝克隆插件。
  2. 克隆用戶需要 BACKUP_ADMIN 權限。

所以,如果要讓克隆操作失敗,任意一個條件不滿足即可。推薦第一個,即不安裝或者卸載克隆插件。

為什么不推薦回收權限這種方式呢?

因為卸載克隆插件這個操作(uninstall plugin clone)不會記錄 Binlog,而回收權限會。

雖然回收權限的操作也可以通過SET SQL_LOG_BIN=0?的方式不記錄 Binlog,但這樣又會導致集群各節點的數據不一致。所以,非常不推薦回收權限這種方式。

所以,如果不想 MGR 從 Primary 節點克隆數據,直接卸載 Primary 節點的克隆插件即可。

問題雖然解決了,但還是有一個疑問:endpoints 中為什么會有多個端點呢?不應該就是 Donor 的實例地址,只有一個么?這個實際上與 group_replication_advertise_recovery_endpoints 有關。

group_replication_advertise_recovery_endpoints

group_replication_advertise_recovery_endpoints 參數是 MySQL 8.0.21 引入的,用來自定義恢復地址。

看下面這個示例。

group_replication_advertise_recovery_endpoints=?"127.0.0.1:3306,127.0.0.1:4567,[::1]:3306,localhost:3306"

在設置時,要求端口必須來自 port、report_port 或者 admin_port。

而主機名只要是服務器上的有效地址即可(一臺服務器上可能存在多張網卡,對應的會有多個 IP),無需在 bind_address 或 admin_address 中指定。

除此之外,如果要通過 admin_port 進行分布式恢復操作,用戶還需要授予 SERVICE_CONNECTION_ADMIN 權限。

下面我們看看 group_replication_advertise_recovery_endpoints 的生效時機。

在選擇完 Donor 后,MGR 會調用get_endpoints來獲取這個 Donor 的 endpoints。

//?plugin/group_replication/src/plugin_variables/recovery_endpoints.cc
Donor_recovery_endpoints::get_endpoints(Group_member_info?*donor)?{...std::vector<std::pair<std::string,?uint>>?endpoints;//?donor->get_recovery_endpoints().c_str()?即?group_replication_advertise_recovery_endpoints?的值if?(strcmp(donor->get_recovery_endpoints().c_str(),?"DEFAULT")?==?0)?{error?=?Recovery_endpoints::enum_status::OK;endpoints.push_back(std::pair<std::string,?uint>{donor->get_hostname(),?donor->get_port()});}?else?{std::tie(error,?err_string)?=check(donor->get_recovery_endpoints().c_str());if?(error?==?Recovery_endpoints::enum_status::OK)endpoints?=?Recovery_endpoints::get_endpoints();}...return?endpoints;
}

如果 group_replication_advertise_recovery_endpoints 為 DEFAULT(默認值),則會將 Donor 的 hostname 和 port 設置為 endpoint。

注意,節點的 hostname、port 實際上就是 performance_schema.replication_group_members 中的 MEMBER_HOST、 MEMBER_PORT。

hostname 和 port 的取值邏輯如下:

//?sql/rpl_group_replication.cc
void?get_server_parameters(char?**hostname,?uint?*port,?char?**uuid,unsigned?int?*out_server_version,uint?*out_admin_port)?{...if?(report_host)*hostname?=?report_host;else*hostname?=?glob_hostname;if?(report_port)*port?=?report_port;else*port?=?mysqld_port;...return;
}

優先使用 report_host、report_port,其次才是主機名、mysqld 的端口。

如果 group_replication_advertise_recovery_endpoints 不為 DEFAULT,則會該參數的值設置為 endpoints。

所以,一個節點,只有被選擇為 Donor,設置的 group_replication_advertise_recovery_endpoints 才會有效果。

而節點有沒有設置 group_replication_advertise_recovery_endpoints 與它能否被選擇為 Donor 沒有任何關系。

總結

  1. MGR 選擇 Donor 是隨機的。
  2. MGR 在執行克隆操作之前,會將 clone_valid_donor_list 設置為 Donor 的 endpoint,所以,在啟動組復制之前,在 mysql 客戶端中顯式設置 clone_valid_donor_list 是沒有效果的。
  3. MGR 執行克隆操作,實際上調用的就是CLONE INSTANCE命令。
  4. performance_schema.replication_group_members 中的 MEMBER_HOST 和 MEMBER_PORT,優先使用 report_host、report_port,其次才是主機名、mysqld 的端口。
  5. 一個節點,只有被選擇為 Donor,設置的 group_replication_advertise_recovery_endpoints 才會有效果。
  6. 如果不想 MGR 從 Primary 節點克隆數據,直接卸載 Primary 節點的克隆插件即可。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/74293.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74293.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74293.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第三十二篇 深入解析Kimball維度建模:構建企業級數據倉庫的完整框架

目錄 一、維度建模設計原則深度剖析1.1 業務過程驅動設計1.2 星型模式VS雪花模式 二、維度建模五步法實戰&#xff08;附完整案例&#xff09;2.1 業務需求映射2.2 模型詳細設計2.3 緩慢變化維處理 三、高級建模技術解析3.1 漸變維度橋接表3.2 快照事實表設計 四、性能優化體系…

IntelliJ IDEA 中 Maven 的 `pom.xml` 變灰帶橫線?一文詳解解決方法

前言 在使用 IntelliJ IDEA 進行 Java 開發時&#xff0c;如果你發現項目的 pom.xml 文件突然變成灰色并帶有刪除線&#xff0c;這可能是 Maven 的配置或項目結構出現了問題。 一、問題現象與原因分析 現象描述 文件變灰&#xff1a;pom.xml 在項目資源管理器中顯示為灰色。…

緩存過期時間之邏輯過期

1. 物理不過期&#xff08;Physical Non-Expiration&#xff09; 定義&#xff1a;在Redis中不設置EXPIRE時間&#xff0c;緩存鍵永久存在&#xff08;除非主動刪除或內存淘汰&#xff09;。目的&#xff1a;徹底規避因緩存自動過期導致的擊穿&#xff08;單熱點失效&#xff…

基于WebAssembly的瀏覽器密碼套件

目錄 一、前言二、WebAssembly與瀏覽器密碼套件2.1 WebAssembly技術概述2.2 瀏覽器密碼套件的需求三、系統設計思路與架構3.1 核心模塊3.2 系統整體架構圖四、核心數學公式與算法證明4.1 AES-GCM加解密公式4.2 SHA-256哈希函數五、異步任務調度與GPU加速設計5.1 異步任務調度5.…

Qt的內存管理機制

在Qt中&#xff0c;顯式使用new創建的對象通常不需要顯式調用delete來釋放內存&#xff0c;這是因為Qt提供了一種基于對象樹(Object Tree)和父子關系(Parent-Child Relationship)的內存管理機制。這種機制可以自動管理對象的生命周期&#xff0c;確保在適當的時候釋放內存&…

數據結構之雙向鏈表-初始化鏈表-頭插法-遍歷鏈表-獲取尾部結點-尾插法-指定位置插入-刪除節點-釋放鏈表——完整代碼

數據結構之雙向鏈表-初始化鏈表-頭插法-遍歷鏈表-獲取尾部結點-尾插法-指定位置插入-刪除節點-釋放鏈表——完整代碼 #include <stdio.h> #include <stdlib.h>typedef int ElemType;typedef struct node{ElemType data;struct node *next, *prev; }Node;//初化鏈表…

【Linux網絡-五種IO模型與阻塞IO】

一、引入 網絡通信的本質就是進程間的通信&#xff0c;進程間通信的本質就是IO&#xff08;Input&#xff0c;Output&#xff09; I/O&#xff08;input/output&#xff09;也就是輸入和輸出&#xff0c;在馮諾依曼體系結構當中&#xff0c;將數據從輸入設備拷貝到內存就叫作…

算法-最大公約數

1、約數&#xff1a; 1.1 試除法求約數 原理&#xff1a;只需要遍歷最小的約數即可&#xff0c;較大的那個可以直接算出來。 import java.util.*; public class Main {static Scanner sc new Scanner(System.in);public static void main(String[] args) {int t sc.nextIn…

湖北楚大夫

品牌出海已成為眾多企業拓展業務、提升競爭力的關鍵戰略。楚大夫(chudafu.com)作為一家專注于品牌出海、海外網絡營銷推廣以及外貿獨立站搭建的公司&#xff0c;憑借其專業、高效、創新的服務模式&#xff0c;致力于成為中國企業走向國際市場的堅實后盾與得力伙伴。楚大夫通過綜…

Flutter 學習之旅 之 flutter 使用 connectivity_plus 進行網路狀態監聽(斷網/網絡恢復事件監聽)

Flutter 學習之旅 之 flutter 使用 connectivity_plus 進行網路狀態監聽&#xff08;斷網/網絡恢復事件監聽&#xff09; 目錄 Flutter 學習之旅 之 flutter 使用 connectivity_plus 進行網路狀態監聽&#xff08;斷網/網絡恢復事件監聽&#xff09; 一、簡單介紹 二、conne…

從零開始實現 C++ TinyWebServer 處理請求 HttpRequest類詳解

文章目錄 HTTP 請求報文HttpRequest 類實現 Init() 函數實現 ParseRequestLine() 函數實現 ParseHeader() 函數實現 ParsePath() 函數實現 ParseBody() 函數實現 ParsePost() 函數實現 ParseFromUrlEncoded() 函數實現 UserVerify() 函數實現 Parse() 函數HttpRequest 代碼Http…

systemd-networkd 的 *.network 配置文件詳解 筆記250323

systemd-networkd 的 *.network 配置文件詳解 筆記250323 查看官方文檔可以用 man systemd.network命令, 或訪問: https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html 名稱 systemd.network — 網絡配置 概要 network.network 描述 一個純…

自定義mavlink 生成wireshark wlua插件錯誤(已解決)

進入正題 python3 -m pymavlink.tools.mavgen --langWLua --wire-protocol2.0 --outputoutput/develop message_definitions/v1.0/development.xml 編譯WLUA的時候遇到一些問題 1.ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERATION_VALID 3765:0:ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERAT…

計算機操作系統(四) 操作系統的結構與系統調用

計算機操作系統&#xff08;四&#xff09; 操作系統的結構與系統調用 前言一、操作系統的結構1.1 簡單結構1.2 模塊化結構1.3 分層化結構1.4 微內核結構1.5 外核結構 二、系統調用1.1 系統調用的基本概念1.2 系統調用的類型 總結&#xff08;核心概念速記&#xff09;&#xf…

深入解析 Spring IOC AOP:原理、源碼與實戰

深入解析 Spring IOC & AOP&#xff1a;原理、源碼與實戰 Spring 框架的核心在于 IOC&#xff08;控制反轉&#xff09; 和 AOP&#xff08;面向切面編程&#xff09;。今天&#xff0c;我們將深入剖析它們的原理&#xff0c;結合源碼解析&#xff0c;并通過 Java 代碼實戰…

LLM之RAG理論(十四)| RAG 最佳實踐

RAG 的過程很復雜&#xff0c;包含許多組成部分。我們如何確定現有的 RAG 方法及其最佳組合&#xff0c;以確定最佳 RAG 實踐&#xff1f; 論文 《Searching for Best Practices in Retrieval-Augmented Generation》給出了回答。 本文將從以下三方面進行介紹&#xff1a; 首先…

利用knn算法實現手寫數字分類

利用knn算法實現手寫數字分類 1.作者介紹2.KNN算法2.1KNN&#xff08;K-Nearest Neighbors&#xff09;算法核心思想2.2KNN算法的工作流程2.3優缺點2.4 KNN算法圖示介紹 3.實驗過程3.1安裝所需庫3.2 MNIST數據集3.3 導入手寫數字圖像進行分類3.4 完整代碼3.5 實驗結果 1.作者介…

C語言-適配器模式詳解與實踐

文章目錄 C語言適配器模式詳解與實踐1. 什么是適配器模式&#xff1f;2. 為什么需要適配器模式&#xff1f;3. 實際應用場景4. 代碼實現4.1 UML 關系圖4.2 頭文件 (sensor_adapter.h)4.3 實現文件 (sensor_adapter.c)4.4 使用示例 (main.c) 5. 代碼分析5.1 關鍵設計點5.2 實現特…

Rust函數、條件語句、循環

文章目錄 函數**語句與表達式**條件語句循環 函數 Rust的函數基本形式是這樣的 fn a_func(a: i32) -> i32 {}函數名是蛇形風格&#xff0c;rust不在意函數的聲明順序&#xff0c;只需要有聲明即可 函數參數必須聲明參數名稱和類型 語句與表達式 這是rust非常重要的基礎…

maptalks圖層交互 - 模擬 Tooltip

maptalks圖層交互 - 模擬 Tooltip 圖層交互-模擬tooltip官方文檔 <!DOCTYPE html> <html><meta charsetUTF-8 /><meta nameviewport contentwidthdevice-width, initial-scale1 /><title>圖層交互 - 模擬 Tooltip</title><style typet…