草庐IT

从源码分析 MySQL Group Replication 的新主选举算法

爱学习的Victor 2023-03-28 原文
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

这几个节点中,最小版本号是 5.7.18,小于 MySQL 8.0.17。所以会比较各个节点的 major_version,因为最后一个节点(8.0.2)的 major_version 和第一个节点不一致,所以会将 8.0.2 作为 lowest_version_end。此时,除了 8.0.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 的节点不会参与排序。

选择 server_uuid 最小的节点作为 Primary 节点。

案例 2:5.7.20, 5.7.21, 8.0.2, 8.0.2

同案例 1 一样,会将 8.0.2 作为 lowest_version_end。此时,候选节点只有 5.7.20 和 5.7.21。

最小版本号 5.7.20 等于 MySQL 5.7.20,所以,5.7.20, 5.7.21 这两个节点会根据节点的权重进行排序。如果权重一致,则会基于 server_uuid 进行进一步的排序。

选择权重最高,server_uuid 最小的节点作为 Primary 节点。

案例 3:8.0.17, 8.0.18, 8.0.19

最小版本号是 MySQL 8.0.17,等于 MySQL 8.0.17,所以会判断其它节点的版本号是否与第一个节点相同。不相同,则会将该节点的版本号赋值给 lowest_version_end。所以,会将 8.0.18 作为 lowest_version_end。此时,候选节点只有 8.0.17。

选择 8.0.17 这个节点作为 Primary 节点。

案例 4:8.0.13, 8.0.17, 8.0.18

最小版本号是 MySQL 8.0.13,小于 MySQL 8.0.17,而且各个节点的 major_version 一致,所以最后返回的 lowest_version_end 实际上是 all_members_info->end()。此时,这三个节点都是候选节点。

MySQL 8.0.13 大于 MySQL 5.7.20,所以这三个节点会根据权重进行排序。如果权重一致,则会基于 server_uuid 进行进一步的排序。

选择权重最高,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 的新主选举算法:

如果集群中存在 MySQL 5.7 的节点,则会将 MySQL 5.7 的节点作为候选节点。

如果集群节点的版本都是 MySQL 8.0,这里需要区分两种情况:

  • 如果最小版本小于 MySQL 8.0.17,则所有的节点都可作为候选节点。
  • 如果最小版本大于等于 MySQL 8.0.17,则只有最小版本的节点会作为候选节点。
在候选节点的基础上,会进一步根据候选节点的权重和 server_uuid 选择 Primary 节点。具体来说,

  • 如果候选节点中存在 MySQL 5.7.20 之前版本的节点,则会选择 server_uuid 最小的节点作为 Primary 节点。
  • 如果候选节点都大于等于 MySQL 5.7.20,则会选择权重最高,server_uuid 最小的节点作为 Primary 节点。

有关从源码分析 MySQL Group Replication 的新主选举算法的更多相关文章

  1. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  2. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  3. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  4. 建模分析 | 平面2R机器人(二连杆)运动学与动力学建模(附Matlab仿真) - 2

    目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标

  5. 网站日志分析软件--让网站日志分析工作变得更简单 - 2

    网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.

  6. elasticsearch源码关于TransportSearchAction【阶段三】 - 2

    1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>

  7. (附源码)vue3.0+.NET6实现聊天室(实时聊天SignalR) - 2

    参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍  介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。  内容有:    ①:Hub模型的方法介绍    ②:服务器端代码介绍    ③:前端vue3安装并调用后端方法    ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke()  去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on

  8. ABB-IRB-1200运动学分析MATLAB RVC工具分析+Simulink-Adams联合仿真 - 2

    一、机器人介绍        此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接

  9. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  10. ruby - 在 Ruby 中实现 Luhn 算法 - 2

    我一直在尝试用Ruby实现Luhn算法。我一直在执行以下步骤:该公式根据其包含的校验位验证数字,该校验位通常附加到部分帐号以生成完整帐号。此帐号必须通过以下测试:从最右边的校验位开始向左移动,每第二个数字的值加倍。将乘积的数字(例如,10=1+0=1、14=1+4=5)与原始数字的未加倍数字相加。如果总模10等于0(如果总和以零结尾),则根据Luhn公式该数字有效;否则无效。http://en.wikipedia.org/wiki/Luhn_algorithm这是我想出的:defvalidCreditCard(cardNumber)sum=0nums=cardNumber.to_s.s

随机推荐