ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MySQL MGR高可用集群实战:从原理到三节点部署

MySQL MGR高可用集群实战:从原理到三节点部署

1. 项目概述:这不是一个“搭个集群就完事”的玩具实验

MySQL-MGR高可用集群方案——案例,这个标题里藏着三个关键信息点:“MySQL”、“MGR”、“高可用集群”。它不是讲怎么装MySQL,也不是教你怎么写SQL,更不是演示单机主从复制的过时套路。它直指一个在真实生产环境中反复被验证、也反复被踩坑的核心命题:当数据库成为业务系统的命脉,如何让它的服务连续性不依赖于某一台物理服务器、某一块硬盘、甚至某一个机柜的供电?MGR(MySQL Group Replication)是MySQL官方在5.7.17版本正式引入的原生分布式一致性协议实现,它用Paxos变种算法替代了传统异步/半同步复制中固有的数据漂移风险,把“主库挂了,从库谁来接?”这个悬而未决的问题,变成了一个可预测、可收敛、可自动仲裁的确定性过程。我参与过的几个中型业务系统迁移,上线前最常被运维总监盯着问的,就是“MGR的脑裂防护机制到底靠不靠谱?”、“切换过程会不会丢事务?”、“监控告警能不能精确到‘当前组内有多少节点在线’这种粒度?”。这些问题的答案,不在官方文档的抽象描述里,而在一次又一次的真实压测、故障注入和灰度发布中。这篇内容,就是把这些散落在不同项目日志、配置文件和深夜值班记录里的经验,拧成一股能直接上手的实操绳索。它适合两类人:一类是正在为新系统选型、纠结于MGR还是Percona XtraDB Cluster的架构师,另一类是已经接到任务、明天就要在测试环境拉起第一个三节点MGR集群的DBA或后端工程师。你不需要先背熟Paxos论文,但得清楚知道,为什么group_replication_consistency=AFTER这个参数,在订单支付场景下比BEFORE_ON_PRIMARY_FAILOVER更能保住用户的钱包余额。

2. 方案设计与核心思路拆解:为什么是MGR,而不是别的?

2.1 MGR不是银弹,但它解决了传统主从最痛的三个点

很多人一上来就想问“MGR和Redis Cluster有啥区别?”,这问题本身就有偏差。MGR解决的从来不是“缓存穿透”或者“热点Key”,而是关系型数据库领域里那个古老又顽固的矛盾:强一致性与高可用性之间的张力。传统一主多从架构,就像一个班级里只允许班长(主库)发号施令,其他同学(从库)只能举手提问、不能擅自回答。好处是秩序井然,坏处是班长一旦请假(宕机),整个班级就陷入沉默(服务中断),而等新班长(手动切换)选出来,中间可能已经漏掉了好几条重要通知(丢失事务)。MGR的思路很“反常识”:它不要求只有一个发号施令者,而是让所有成员(节点)都具备“临时班长”的资格,但必须通过一套严格的投票规则(基于Paxos的组通信协议)来确认,此刻谁说的话才真正算数。这套规则确保了三件事:第一,任何写入操作,必须得到组内大多数节点(quorum)的确认才能成功提交,这就从源头杜绝了“主库写了但从库没收到”的数据不一致;第二,当某个节点失联,剩余节点会自动重新计算法定人数(比如3节点组,只要2个在线,就能继续提供服务),避免了传统主从里“主库挂了,从库不敢动”的僵局;第三,它内置了自动故障检测与恢复机制,当一个失联节点重新连入,MGR会自动判断它是否“掉队”太远,如果只是短暂网络抖动,就增量同步缺失的数据,如果已经落后太多,则直接拒绝其加入,强制要求它先做全量恢复(Clone),防止一个脏数据节点污染整个组。这三点,恰恰对应了我们线上最怕的三种故障:主库磁盘损坏导致的静默数据损坏、跨机房网络分区引发的脑裂、以及运维误操作导致的从库延迟爆炸。MGR不是让故障不发生,而是让故障发生后的系统行为变得可预期、可管理。

2.2 为什么放弃MHA、Orchestrator这些成熟工具?

在决定采用MGR之前,我们团队花了整整两周时间,把市面上主流的MySQL高可用方案都跑了一遍对比测试。MHA(Master High Availability)确实稳定,社区资料丰富,很多老系统都在用。但它有一个致命的软肋:故障切换是“尽力而为”的。它依赖外部脚本去探测主库心跳,一旦探测失败,就立刻执行预设的failover脚本,把某个从库提升为主库。这个过程快则十几秒,慢则一分钟以上,而且没有任何机制能保证,在主库宣布“死亡”的那一刻,它刚刚处理的那个事务,是否已经被同步给了即将被提拔的从库。我们做过一个模拟实验:在主库执行一条UPDATE account SET balance = balance - 100 WHERE id = 123;,紧接着立刻切断主库网络,MHA在3秒后完成切换。结果发现,新主库上的balance字段,比旧主库少了100块。这就是典型的“切换丢失”。Orchestrator更进一步,提供了Web界面和更精细的拓扑管理,但它底层依然是基于传统复制的逻辑,无法根除数据一致性这个根本问题。而MGR,把一致性保障下沉到了存储引擎层(InnoDB)和网络协议层。它要求每个事务在提交前,必须先广播给组内其他节点,并获得多数派的ACK,这个ACK不是简单的“我收到了”,而是包含了对事务全局顺序(GTID)的严格校验。这意味着,只要事务在旧主库上成功提交了,它就必然已经存在于至少两个节点的binlog里。所以MGR的切换,本质上不是“找一个新主库”,而是“确认哪个节点拥有最新的、已被多数派认可的事务序列”。这个逻辑上的根本差异,决定了MGR的切换是“零数据丢失”的(前提是配置正确),而MHA/Orchestrator的切换,永远存在一个微小但真实存在的“窗口期”。

2.3 单主模式 vs 多主模式:我们为什么死守单主?

MGR官方文档里明确支持两种运行模式:Single-Primary Mode(单主)和Multi-Primary Mode(多主)。初看之下,多主模式似乎更“高大上”,所有节点都能读写,理论上吞吐量翻倍。但我们在一个电商促销活动的压测中,亲手把它按进了坑里。问题出在应用层的“乐观锁”机制上。我们的库存扣减逻辑是:SELECT stock FROM item WHERE id = ? FOR UPDATE;然后在应用里判断stock > 0,再执行UPDATE item SET stock = stock - 1 WHERE id = ? AND stock > 0;。在单主模式下,所有写请求都打向同一个节点,FOR UPDATE锁是全局有效的,不会出现超卖。但在多主模式下,两个用户几乎同时下单,请求分别打到了Node A和Node B。Node A查到stock=1,Node B也查到stock=1,它们各自都认为可以扣减,于是都执行了UPDATE。由于MGR的冲突检测是在事务提交阶段才进行的,此时两个事务都已本地提交,但当它们试图将各自的变更广播给对方时,MGR检测到对同一行item.id=123的并发更新,会强制回滚其中一个事务,并抛出ER_GR_HOLD_WAIT_TIMEOUT错误。结果就是,用户看到的是“下单失败,请重试”,体验极差。更麻烦的是,这个错误不是在应用发起SQL时立刻返回的,而是在事务提交的瞬间才爆发,导致应用层的重试逻辑变得异常复杂。单主模式虽然牺牲了一定的写扩展性,但它换来了应用逻辑的极度简化。所有写操作的顺序性、锁的全局有效性,都由MGR内部的单主仲裁器(Primary Election)来保证。我们宁愿把读请求分散到所有节点(MGR天然支持读扩展),把写压力集中在主节点,再通过分库分表、读写分离中间件来横向扩展,也不愿为了那点理论上的写吞吐,去挑战应用层的并发控制逻辑。这是我们在多个项目里用真金白银买来的教训:数据库的高可用,最终要服务于业务的稳定,而不是技术指标的漂亮。

3. 核心细节解析与实操要点:配置不是填空,是精密调校

3.1 网络与硬件:别让千兆网卡毁了你的Paxos

MGR对网络质量的敏感度,远超你的想象。它不是一个简单的“主库发binlog,从库收binlog”的模型,而是一个需要所有节点之间频繁交换心跳、认证、事务消息的组通信系统。我们最初在一个测试环境里,用的是虚拟机+共享存储,网络配置是默认的千兆网卡。在模拟一个节点网络延迟时,我们发现,当延迟超过150ms,整个组就会开始出现“假性失联”:节点A认为B死了,B认为A死了,但其实它们只是互相“听不清”。这是因为MGR的group_replication_member_expel_timeout(默认值为0,即禁用自动驱逐)和group_replication_unreachable_majority_timeout(默认值为0)这两个参数,在默认情况下,不会主动驱逐一个只是“慢”的节点,而是选择等待。等待的结果,就是整个组的服务能力急剧下降,因为每次写入都需要等待所有在线节点的响应,而那个“慢节点”成了木桶的短板。解决方案不是简单地调大超时时间,而是从根本上优化网络。我们最终的生产环境标准是:所有MGR节点必须部署在同一物理机房内,使用万兆专用网络(非业务网),并且在操作系统层面,关闭了所有可能影响TCP性能的特性,比如tcp_tw_reuse(在TIME_WAIT状态下重用socket)和net.ipv4.tcp_fin_timeout(缩短FIN超时时间)。更重要的是,我们为MGR流量单独配置了QoS策略,确保它的网络包优先级高于普通业务流量。硬件上,我们强制要求所有节点使用SSD,并且innodb_flush_log_at_trx_commit=1和sync_binlog=1这两个参数必须开启。有人会觉得这太“奢侈”,会拖慢写入速度。但请记住,MGR的写入瓶颈从来不在磁盘IO,而在于网络往返(RTT)和Paxos协议的协商开销。一个慢的磁盘,只会让你的单次写入变慢;而一个不稳定的网络,会让你的整个集群陷入不可用的泥潭。我们做过对比:在万兆网络+SSD环境下,一个简单的INSERT语句的平均延迟是12ms;在千兆网络+HDD环境下,同样的语句,延迟波动范围是8ms到230ms,后者在高并发下,极易触发MGR的超时保护机制。

3.2 关键参数详解:每一个数字背后都是血泪史

MGR的配置文件里,有十几个参数看似不起眼,但任何一个配错,都可能导致集群启动失败、切换异常,甚至数据不一致。下面这几个,是我们踩坑最多、也最重要的:

  • group_replication_group_name:这是一个UUID格式的字符串,必须在组内所有节点上完全一致。它不是随便生成一个就行,我们有一个硬性规定:这个UUID必须和该集群的业务标识强绑定。比如,订单库集群,我们就用e9a6b3c1-2f4d-4a8e-9b1c-7d8e6f5a4b3c,并在CMDB里做好登记。这样做的好处是,当某台机器上意外启动了一个同名的MGR实例(比如测试脚本没清理干净),它会立刻被现有集群识别为“非法入侵者”并拒绝加入,而不是悄无声息地造成混乱。

  • group_replication_local_address:这是该节点对外广播的地址,必须是IP,不能是主机名。我们吃过一次大亏:在DNS配置里,把db-node1解析到了一个内网IP,但该节点的my.cnf里却配置了group_replication_local_address=db-node1:33061。结果MGR启动后,它向外广播的是db-node1:33061,而其他节点尝试连接时,会去解析db-node1,但解析出来的IP和它自己广播的IP不一致,导致握手失败。后来我们统一规定,所有MGR相关地址,一律使用ifconfig命令查到的、绑定在业务网卡上的真实IP。

  • group_replication_start_on_boot=OFF:这个参数必须设为OFF!这是官方强烈建议的,也是我们血的教训。如果设为ON,MySQL服务启动时会自动尝试加入集群。但如果此时集群的其他节点还没起来,或者网络还没通,它就会进入一个“无限重试”的状态,卡住整个MySQL服务的启动流程,导致监控告警一片红。正确的做法是,写一个独立的初始化脚本,在确认所有节点网络可达、MySQL服务已启动后,再通过mysql -e "START GROUP_REPLICATION;"命令,手动、有序地启动每个节点的MGR。

  • group_replication_consistency:前面提到过,我们选择了AFTER。它的含义是:在当前事务提交之后,该事务所修改的数据,对后续的所有读取操作(无论在哪个节点上)都是可见的。这听起来像废话,但它是解决“读取延迟”问题的终极武器。在BEFORE模式下,一个事务在主库提交后,从库可能还没同步完,此时应用如果去从库读,就可能读到旧数据。而AFTER模式,会强制让主库在提交前,先等待所有从库都确认收到了这个事务,才允许提交。这会带来一点性能损耗(大约5%-10%的TPS下降),但换来的是应用层彻底摆脱“读己之写”(Read-Your-Writes)的一致性难题。对于金融、订单这类强一致性场景,这点损耗是绝对值得的。

3.3 安全与权限:最小权限原则不是口号

MGR节点之间需要建立一个独立的、加密的组通信通道,这个通道的认证,是通过MySQL自身的用户权限系统来完成的。我们曾经因为一个疏忽,在一个节点上创建了repl@'%'这样的宽泛用户,结果导致集群被一个来自外网的扫描器暴力破解,差点被注入恶意SQL。从此,我们制定了铁律:MGR专用用户,必须遵循“三唯一”原则——唯一用户名(如mgr_user)、唯一密码(长度20位以上,含大小写字母、数字、特殊字符)、唯一来源主机(mgr_user@'10.10.10.%',精确到子网段)。创建用户的SQL语句,必须是:

CREATE USER 'mgr_user'@'10.10.10.%' IDENTIFIED WITH 'caching_sha2_password' BY 'YourSuperStrongPasswordHere!'; GRANT REPLICATION SLAVE ON *.* TO 'mgr_user'@'10.10.10.%'; GRANT GROUP_REPLICATION_ADMIN ON *.* TO 'mgr_user'@'10.10.10.%'; FLUSH PRIVILEGES;

注意,这里用了caching_sha2_password插件,这是MySQL 8.0的默认认证方式,比老的mysql_native_password更安全。GROUP_REPLICATION_ADMIN权限是MGR 8.0.13之后新增的,专门用于管理组复制,比SUPER权限更细粒度,也更安全。另外,group_replication_recovery_get_public_key=ON这个参数也必须开启,它会让MGR在恢复过程中,自动获取并验证对方节点的公钥,防止中间人攻击。安全不是加一道防火墙就万事大吉,而是从用户、密码、网络、协议四个层面,层层设防。

4. 实操过程与核心环节实现:从零到三节点集群的完整路径

4.1 环境准备与前置检查:磨刀不误砍柴工

在动手敲任何一条START GROUP_REPLICATION;命令之前,我们必须完成一份详尽的“战前检查清单”。这份清单不是走形式,而是我们多次集群启动失败后总结出的救命稻草。它分为三个层次:

第一层:系统级检查

  • 检查所有节点的/etc/hosts文件,确保每个节点的IP和主机名映射准确无误,且没有重复或错误的条目。我们曾因一个节点的/etc/hosts里多了一行127.0.0.1 db-node2,导致该节点在启动时,尝试用localhost去连接自己,结果连接到了127.0.0.1的3306端口,而非真正的MGR端口33061,从而启动失败。
  • 检查所有节点的系统时间,必须使用NTP服务同步,误差不能超过1秒。MGR的GTID和Paxos协议都严重依赖时间戳,时间不同步会导致事务排序混乱,轻则同步延迟,重则集群分裂。
  • 检查所有节点的ulimit -n(文件描述符限制),必须大于65535。MGR在高并发下会建立大量socket连接,如果限制过低,会出现“Too many open files”的错误。

第二层:MySQL配置检查

  • 确保server_id在组内唯一,且不为0或1(MySQL官方建议从10000开始)。
  • 确保gtid_mode=ON和enforce_gtid_consistency=ON已启用。这是MGR的硬性要求,缺一不可。
  • 确保binlog_format=ROW。MGR只支持基于行的复制,语句级(STATEMENT)或混合级(MIXED)都会导致启动失败。
  • 检查max_allowed_packet,必须足够大(我们设为1G),以容纳可能的大事务。

第三层:网络连通性检查

  • 在Node1上,执行telnet Node2_IP 33061,确保端口可达。
  • 在Node1上,执行nc -zv Node2_IP 33061,这是更可靠的端口探测方式。
  • 最关键的一步:在Node1上,用MGR专用用户,尝试登录Node2的MySQL实例:mysql -h Node2_IP -u mgr_user -p'password' -P 3306。这一步是为了验证MySQL服务本身是否正常,以及用户权限是否正确。只有当这三步全部通过,我们才敢进行下一步。

4.2 集群初始化:从“孤岛”到“联邦”的第一步

集群的初始化,必须严格按照“先建组,再加人”的顺序。我们把第一个节点称为“引导节点”(Bootstrap Node),它的使命是创建一个全新的、空的组。操作步骤如下:

  1. 在Node1上,执行引导命令:

    SET SQL_LOG_BIN=0; CREATE USER 'mgr_user'@'10.10.10.%' IDENTIFIED WITH 'caching_sha2_password' BY 'YourSuperStrongPasswordHere!'; GRANT REPLICATION SLAVE ON *.* TO 'mgr_user'@'10.10.10.%'; GRANT GROUP_REPLICATION_ADMIN ON *.* TO 'mgr_user'@'10.10.10.%'; FLUSH PRIVILEGES; SET SQL_LOG_BIN=1;

    这里SET SQL_LOG_BIN=0是为了防止创建用户的操作被记录到binlog,从而在后续节点加入时被重复执行,造成权限混乱。

  2. 配置Node1的MGR参数(在my.cnf中):

    [mysqld] # ... 其他常规配置 ... server_id=10001 gtid_mode=ON enforce_gtid_consistency=ON binlog_format=ROW # MGR相关 plugin_load_add='group_replication.so' transaction_write_set_extraction=XXHASH64 group_replication_group_name="e9a6b3c1-2f4d-4a8e-9b1c-7d8e6f5a4b3c" group_replication_local_address="10.10.10.11:33061" group_replication_group_seeds="10.10.10.11:33061,10.10.10.12:33061,10.10.10.13:33061" group_replication_bootstrap_group=ON group_replication_start_on_boot=OFF group_replication_consistency=AFTER
  3. 重启Node1的MySQL服务,然后登录,执行:

    START GROUP_REPLICATION;

    注意,group_replication_bootstrap_group=ON这个参数,只在第一次启动引导节点时设置为ON,且在启动成功后,必须立即改为OFF。否则,下次MySQL重启时,它会再次尝试引导一个新组,导致集群分裂。我们把这个操作写进了自动化脚本,确保万无一失。

  4. 验证Node1状态:

    SELECT * FROM performance_schema.replication_group_members;

    正常情况下,应该看到一行记录,MEMBER_STATE为ONLINE,MEMBER_ROLE为PRIMARY。至此,一个单节点的“微型联邦”诞生了。

4.3 节点加入:让“联邦”真正壮大

Node2和Node3的加入,是整个过程中最考验耐心的环节。它们不能像Node1那样“自立门户”,而是必须作为“申请者”,向现有的组发起加入请求。步骤如下:

  1. 在Node2上,配置my.cnf(与Node1基本相同,但有三处关键不同):

    server_id=10002 group_replication_local_address="10.10.10.12:33061" group_replication_group_seeds="10.10.10.11:33061,10.10.10.12:33061,10.10.10.13:33061" group_replication_bootstrap_group=OFF # 必须为OFF! group_replication_start_on_boot=OFF
  2. 重启Node2的MySQL服务,然后登录,执行:

    -- 同样需要先创建用户(如果还没创建的话) SET SQL_LOG_BIN=0; CREATE USER 'mgr_user'@'10.10.10.%' IDENTIFIED WITH 'caching_sha2_password' BY 'YourSuperStrongPasswordHere!'; GRANT REPLICATION SLAVE ON *.* TO 'mgr_user'@'10.10.10.%'; GRANT GROUP_REPLICATION_ADMIN ON *.* TO 'mgr_user'@'10.10.10.%'; FLUSH PRIVILEGES; SET SQL_LOG_BIN=1; -- 开始加入 START GROUP_REPLICATION;
  3. 等待并观察:这个过程可能需要10-30秒。期间,Node2会经历RECOVERING->ONLINE的状态变化。你可以实时监控:

    SELECT MEMBER_ID, MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members;

    当看到Node2的状态变为ONLINE,角色为SECONDARY时,说明加入成功。

  4. 重复步骤1-3,加入Node3。注意,Node3的server_id必须是10003,local_address必须是10.10.10.13:33061。

提示:节点加入不是“一蹴而就”,而是一个需要时间同步数据的过程。如果Node2或Node3之前有大量历史数据,MGR会自动触发Clone(如果启用了group_replication_clone_threshold)或基于binlog的增量恢复。这个过程可能会持续几分钟,期间节点状态会显示为RECOVERING,这是正常现象,切勿强行中断。

4.4 验证与压测:用真实流量检验“纸面方案”

集群搭建完成,只是万里长征第一步。真正的考验,在于它能否扛住真实的业务压力。我们的验证分为三个阶段:

第一阶段:基础功能验证

  • 在Node1(Primary)上执行一个写入:INSERT INTO test_table VALUES (1, 'test');
  • 立刻在Node2和Node3上执行SELECT * FROM test_table;,确认数据已同步。
  • 手动停止Node1的MySQL服务,观察performance_schema.replication_group_members视图,确认Node2或Node3中的一个,其MEMBER_ROLE已变为PRIMARY。
  • 在新的Primary上执行写入,再在其他节点上验证读取,确认切换成功且数据不丢失。

第二阶段:一致性压测我们使用sysbench工具,编写了一个模拟订单创建的Lua脚本,它会循环执行:

-- 1. 开启事务 -- 2. 查询用户余额 -- 3. 查询商品库存 -- 4. 扣减余额和库存 -- 5. 插入订单记录 -- 6. 提交事务

我们将压测流量,通过一个简单的负载均衡器(如HAProxy),随机分发到三个节点的3306端口。重点监控两个指标:一是performance_schema.replication_group_member_stats视图中的COUNT_TRANSACTIONS_IN_QUEUE(事务队列长度),如果这个值持续大于0,说明写入压力已经超过了集群的处理能力;二是information_schema.INNODB_METRICS中的lock_row_lock_time_avg(平均行锁等待时间),如果这个值飙升,说明应用层的锁竞争过于激烈,需要优化SQL或业务逻辑。

第三阶段:故障注入演练这是最残酷,也最有价值的环节。我们会在业务低峰期,进行以下几种故障模拟:

  • 网络隔离:用iptables命令,阻断Node1和Node2之间的33061端口通信,观察集群是否会分裂成两个独立的组(脑裂)。正确的表现是,剩余的两个节点(Node2和Node3)会形成一个新的、包含2个节点的组,而Node1会因为无法达到法定人数(2/3)而自动退出组,状态变为UNREACHABLE。
  • 磁盘满:在Node3上,用dd命令快速占满其/var/lib/mysql所在分区的空间,观察MGR是否会自动将其驱逐。正确的表现是,Node3的状态会变为ERROR,并被其他节点从组中移除。
  • CPU打满:在Node2上,用stress-ng --cpu 4 --timeout 60s命令,让其CPU持续100%,观察写入延迟是否显著增加。这能帮助我们评估集群在资源瓶颈下的降级能力。

每一次故障注入后,我们都会记录下从故障发生,到集群自动恢复稳定,再到所有节点状态恢复正常所花费的时间。这个时间,就是我们SLA承诺的“RTO”(Recovery Time Objective)。

5. 常见问题与排查技巧实录:那些凌晨三点的告警电话

5.1 “MEMBER_STATE=UNREACHABLE”:不是节点挂了,是它“失联”了

这是MGR集群里最常见的告警。很多新手看到这个状态,第一反应就是“赶紧SSH上去看看MySQL是不是挂了”。但往往,systemctl status mysqld显示一切正常。这时,你需要冷静下来,按照以下顺序排查:

  1. 检查网络:在其他节点上,ping和telnet目标节点的MGR端口(33061)。如果ping不通,那就是网络层问题,联系网络工程师。如果ping通但telnet不通,检查目标节点的防火墙(firewalld或iptables)是否放行了33061端口。

  2. 检查MySQL进程:在目标节点上,执行ps aux | grep mysqld,确认MySQL进程确实在运行,并且监听的是0.0.0.0:33061,而不是127.0.0.1:33061。后者意味着它只接受本地连接,其他节点自然连不上。

  3. 检查MGR状态日志:在目标节点的MySQL错误日志(通常是/var/log/mysqld.log)里,搜索关键词GroupReplication。你很可能会看到类似这样的错误:

    [ERROR] Plugin group_replication reported: 'The member was expelled from the group because it was unable to handle incoming transactions for more than 30 seconds.'

    这说明,该节点因为某种原因(可能是磁盘IO慢、CPU满、内存不足),处理事务的速度跟不上,被其他节点“投票”踢出了组。这时,你需要查看系统资源监控(top,iostat,free -h),定位瓶颈。

注意:UNREACHABLE状态是MGR的一种自我保护机制,它比OFFLINE更严重。OFFLINE通常意味着节点主动退出,而UNREACHABLE意味着它被“驱逐”。被驱逐的节点,需要手动清理其mysql.group_replication_group_members表中的残留记录,并重启MySQL服务,才能重新申请加入。

5.2 “Can't connect to MySQL server on 'xxx' (111)”:端口冲突的隐形杀手

这个报错看起来像是网络问题,但很多时候,罪魁祸首是端口冲突。MGR的group_replication_local_address参数,指定了该节点用于组内通信的端口(默认33061)。如果这个端口被其他进程(比如另一个MySQL实例、或者一个调试用的Python HTTP服务)占用了,MGR就无法绑定,从而导致启动失败。排查方法很简单:

# 在目标节点上执行 lsof -i :33061 # 或者 netstat -tuln | grep :33061

如果输出显示有其他进程在监听这个端口,那就需要要么杀掉那个进程,要么修改MGR的端口。我们推荐后者,因为修改端口更安全。修改方法是在my.cnf中,将group_replication_local_address和group_replication_group_seeds里的端口号,统一改成一个不常用的端口,比如33071。

5.3 “The member is not in the group”:配置文件里的“幽灵”参数

当你在节点上执行START GROUP_REPLICATION;,却收到这个错误,说明该节点的MGR配置,与集群的“共识”不匹配。最常见的原因,是group_replication_group_name不一致。但有时候,这个参数明明看着一样,却还是报错。这时,你需要怀疑“看不见的字符”。我们遇到过一次,一个同事在Windows上用记事本编辑my.cnf,保存时编码是UTF-8 with BOM(字节顺序标记),这个BOM字符被MySQL读取后,会变成group_replication_group_name值的一部分,导致它和Node1上纯ASCII的UUID不匹配。解决方案是,所有配置文件,必须用vim或nano等Linux原生编辑器,在UTF-8 without BOM模式下编辑。一个快速验证方法是:

# 在Node1上 SELECT @@group_replication_group_name; # 在Node2上 SELECT @@group_replication_group_name; # 然后用shell命令比较 echo "Node1_UUID" | md5sum echo "Node2_UUID" | md5sum

如果MD5值不同,那一定是UUID字符串里混入了不可见字符。

5.4 切换后应用连接失败:别忘了VIP和DNS

MGR的自动切换,只改变了数据库内部的主从角色,它并不会自动修改你的应用连接字符串。如果你的应用是硬编码了jdbc:mysql://db-node1:3306/mydb,那么当Node1挂掉,Node2成为新主库后,应用的写请求依然会发往已经下线的Node1,导致连接超时。解决这个问题,有两条路:

  • 方案A(推荐):使用中间件。引入一个轻量级的代理,比如MySQL Router。它是一个官方提供的、专为MGR设计的路由组件。你只需要在应用里配置jdbc:mysql://mysql-router:3306/mydb,Router会自动感知MGR集群的状态,并将写请求转发给当前的Primary,将读请求轮询分发给所有Online节点。Router的配置极其简单,只需指定MGR集群的种子节点即可。

  • 方案B:使用VIP(虚拟IP)。在集群的三个节点上,配置一个浮动的VIP(如10.10.10.100)。当某个节点成为Primary时,脚本自动将VIP绑定到它的网卡上。应用始终连接VIP。这个方案的好处是侵入性小,但缺点是需要额外的脚本和健康检查,且在云环境中(如AWS、阿里云)可能受限于平台的网络策略。

无论选择哪种方案,核心思想都是一样的:应用不应该关心“谁是主库”,它只应该关心“哪里能提供服务”。把服务发现的逻辑,从应用层下沉到基础设施层,这才是现代高可用架构的精髓。

6. 监控与告警:让集群状态“看得见、摸得着”

一个没有监控的高可用集群,就像一辆没有仪表盘的赛车。你不知道油量还剩多少,不知道水温是否过高,直到引擎突然熄火。MGR的监控,不能只停留在“集群是否在线”这种粗粒度上,而要深入到协议层、事务层、网络层。

6.1 核心监控指标与采集方式

我们使用Prometheus+mysqld_exporter作为监控栈,以下是必须采集的5个黄金指标:

指标名称Prometheus指标名采集方式告警阈值说明
集群在线节点数mysql_global_status_group_replication_members_onlinemysqld_exporter< 23节点集群,低于2意味着法定人数不满足,集群已丧失写入能力。
主库事务延迟mysql_global_status_group_replication_transactions_waiting_apply_countmysqld_exporter> 10表示当前Primary上有超过10个事务在等待被应用到InnoDB,说明从库同步有严重积压。
**网络消息
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进