爱学it学无止境
MGR 中,新节点在加入时,为了与组内其它节点的数据保持一致,它会首先经历一个分布式恢复阶段。在这个阶段,新节点会随机选择组内一个节点(Donor)来同步差异数据。
在 MySQL 8.0.17 之前,同步的方式只有一种,即基于 Binlog 的异步复制,这种方式适用于差异数据较少或需要的 Binlog 都存在的场景。
从 MySQL 8.0.17 开始,新增了一种同步方式-克隆插件,克隆插件可用来进行物理备份恢复,这种方式适用于差异数据较多或需要的 Binlog 已被 purge 的场景。
克隆插件虽然极大提升了恢复的效率,但备份毕竟是一个 IO 密集型的操作,很容易影响备份实例的性能,所以,我们一般不希望克隆操作在 Primary 节点上执行。
但 Donor 的选择是随机的(后面会证明这一点),有没有办法让 MGR 不从 Primary 节点克隆数据呢?
本文主要包括以下几部分:
MGR 是如何执行克隆操作的? 可以通过 clone_valid_donor_list 设置 Donor 么? MGR 是如何选择 Donor 的? MGR 克隆操作的实现逻辑。 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 可分为以下两步:
首先,判断哪些节点适合当 Donor。满足条件的节点会放到一个动态数组(m_suitable_donors)中, 这个操作是在Remote_clone_handler::get_clone_donors函数中实现的。 其次,循环遍历 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; } 该函数的处理流程如下:
获取集群所有节点的信息,存储到 all_members_info 中。
all_members_info 是个动态数组,数组中的元素是按照节点 server_uuid 从小到大的顺序依次存储的。
通过vector_random_shuffle函数将 all_members_info 进行随机重排。
选择 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 克隆操作的实现逻辑。