首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

如何让 MGR 不从 Primary 节点克隆数据?

编程知识
2024年07月22日 06:53

问题

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;");
  else
    query.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 list
    if (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_list
      if ((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 issue
      if (!critical_error) critical_error = evaluate_error_code(error);

      // On ER_RESTART_SERVER_FAILED it makes no sense to retry
      if (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 节点的克隆插件即可。

延伸阅读

  1. MySQL 8.0 新特性之 Clone Plugin
  2. 《MySQL实战》组复制章节
From:https://www.cnblogs.com/ivictor/p/18315272
本文地址: http://www.shuzixingkong.net/article/268
0评论
提交 加载更多评论
其他文章 【SQL】Lag/Rank/Over窗口函数揭秘,数据分析之旅
七月的夏日,阳光如火,但小悦的心中却是一片清凉与激情。在数据分析项目组的新岗位上,她仿佛找到了自己新的舞台,这里让她得以将深厚的后端技术实力与数据分析的精髓深度融合。每天,她都沉浸在业务需求的分析与数据驱动的决策之中,与业务、产品等多部门紧密合作,共同揭开数据背后的秘密,为企业的发展贡献自己的力量。
强烈推荐!!!阿里旗下10款顶级开源项目
大家好,我是晓凡 写在前面 如果你是一名Java开发者,下面说到的这些开源项目应该不陌生了。在实际工作中或多或少都用到过。 趁着周末,特地整理下阿里巴巴旗下开源的10款顶级项目 TOP10 rocketmq ① 简介 RocketMQ 是一个分布式消息中间件。专门负责在不同的软件系统之间传递消息 最
强烈推荐!!!阿里旗下10款顶级开源项目 强烈推荐!!!阿里旗下10款顶级开源项目 强烈推荐!!!阿里旗下10款顶级开源项目
AI时代你应聚焦的领域在哪里
随着AI的飞速发展,把我们带到了一个全新的时代。每个人都应该积极拥抱AI,让AI给我们提效。那不同的人群应该聚焦在哪里呢?
AI时代你应聚焦的领域在哪里
关键点检测(1)——标注关键点检测数据(labelme和CVAT)
关键点检测,作为计算机视觉领域的重要分支,广泛应用于人体姿态估计、面部表情识别、手部动作分析等多个场景。其核心在于从图像中准确检测并定位特定的关键点位置。然而,高效的模型训练离不开大量高质量的标注数据。本文将详细介绍关键点检测数据的标注方法,包括标注工具的选择、标注流程以及注意事项,帮助自己深入理解
关键点检测(1)——标注关键点检测数据(labelme和CVAT) 关键点检测(1)——标注关键点检测数据(labelme和CVAT) 关键点检测(1)——标注关键点检测数据(labelme和CVAT)
记一次 Redisson 线上问题 → 你怎么能释放别人的锁
开心一刻 今天,我的又一个好哥们脱单了,只剩下我自己单身了 我向一个我喜欢的女生吐苦水 我:我这辈子是找不到女朋友了 她:怎么可能,你很优秀的,会有很多女孩子愿意当你女朋友的 我内心窃喜,问道:那你愿意当我女朋友吗 她:我都在开导你了,你不要恩将仇报! 线上问题 生产环境突然告警,告警信息: att
记一次 Redisson 线上问题 → 你怎么能释放别人的锁 记一次 Redisson 线上问题 → 你怎么能释放别人的锁 记一次 Redisson 线上问题 → 你怎么能释放别人的锁
都2024年了你还傻傻分不清楚“编译时”和“运行时”吗?
在写vue3编译原理揭秘电子书的时候,发现有不少粉丝还傻傻分不清楚什么是编译时?什么是运行时?这篇文章我们来让你彻底搞清楚编译时和运行时的区别。
都2024年了你还傻傻分不清楚“编译时”和“运行时”吗? 都2024年了你还傻傻分不清楚“编译时”和“运行时”吗? 都2024年了你还傻傻分不清楚“编译时”和“运行时”吗?
如何平稳地从nacos迁移到r-nacos?
很多同学了解r-nacos特性后最开始只将r-nacos用于开发测试环境。 经过一段时间的使用后,部分同学有打算生成环境也从nacos迁移到r-nacos。 一些之前使用nacos服务的同学了解r-nacos后打算从nacos迁移到r-nacos。 那么如何平衡地从nacos迁移到r-naco
如何平稳地从nacos迁移到r-nacos? 如何平稳地从nacos迁移到r-nacos? 如何平稳地从nacos迁移到r-nacos?
深入浅出分析最近火热的Mem0个性化AI记忆层
最近Mem0横空出世,官方称之为PA的记忆层,The memory layer for Personalized AI,有好事者还称这个是RAG的替代者,Mem0究竟为何物,背后的原理是什么,我们今天来一探究竟。