☰
MGR集群SEC节点ERROR状态排查与恢复全流程实战
2026/10/5 7:37:57 网站建设 项目流程

半夜两点,监控平台弹出一条红色告警:MGR 集群里有节点状态不是 ONLINE。点开详情,member_state 是 ERROR,角色是 SECONDARY,也就是我们日常说的 SEC 节点。那一瞬间的紧张感,维护过 MySQL Group Replication(MGR)的人应该都懂——SEC 节点平时安安静静待在角落里,一旦掉进 ERROR,不只是高可用冗余少了一台这么简单,更意味着后续主库如果出问题,你可能连一个可靠的切换对象都没有。这篇文章不聊泛泛的架构理论,直接把我维护的一套一主两从 MGR 集群里,SEC 节点报 ERROR 之后的分析、处理、复盘全过程写出来,包括先查哪几张状态视图、怎么从错误日志拉时间线、如何一步步把节点拉回 ONLINE,以及常规手册里不会写的那些坑。适合正在管理 MGR 集群的 DBA 和运维同学参考,也适合刚接触组复制的人建立一套排障大脑图。

1. 先把SEC节点ERROR这件事看透

很多人一看到 ERROR 就急着重启,其实先把“它为什么会变成 ERROR”搞清楚,比直接操作重要得多。MGR 里每个节点都要参与分布式事务的认证和提交,SEC 节点虽然不写业务数据,却是这个协议闭环里实实在在的一环。状态一掉,整个组的决策能力都会被打折扣。

1.1 组复制成员状态机:ONLINE不是常态

MGR 对每个成员都维护一套状态机,在performance_schema.replication_group_members里可以直接看到:

  • ONLINE:节点正常参与组复制事务,收发消息都正常。
  • RECOVERING:节点正在加入组,或者正在追赶组内缺失的事务,这个状态可以持续几分钟甚至更久。
  • ERROR:节点在组内被判定为异常,无法参与组复制协议,通常需要人工干预。
  • OFFLINE:该实例上的组复制插件处于停止状态,可能是手动停的,也可能是启动失败。
  • UNREACHABLE:组内其他成员无法与该节点通信,在 8.0 里比较常见。

这套状态不是 MySQL 服务本身的进程状态,而是组通信层(GCS)维护的分布式状态。每个成员会周期性地给其他成员发送心跳消息,如果连续收不到某成员的响应,系统就会进入“疑似故障”流程。超过一定时间后,其他成员会把该节点从组里踢出去,于是它在成员视图里的状态就会从 ONLINE 跌到 ERROR 或 UNREACHABLE。我见过很多同学一看到 ERROR 就以为 mysqld 崩了,其实不少场景下进程还活着,只是组复制这个“身份”掉了。

1.2 为什么掉进ERROR的往往是SEC节点

单主模式下,PRIMARY 只有一台,它要对外提供写服务,一旦它出问题,整个组会很快感知并发起新的选主,所以主库的问题往往藏不住。SEC 节点不一样:它要承担两件事,一是通过 group_replication_applier 通道回放主库的写入,二是参与组内事务的认证和一致性协议。当主库有大事务、DDL,或者高峰期写入量比较大时,SEC 节点的回放队列会肉眼可见地积压。积压一旦超过故障检测的容忍范围,它就会被其他成员判定为“无响应”,随后被驱逐。

还有一点容易被忽略:SEC 节点通常也在承接读流量。读压力大起来会抢占 CPU 和 IO,间接拖慢 applier 线程。所以我的经验是,SEC 节点出 ERROR,不要只怪网络,先看它的回放队列顶了多久。排队越长,它进入 RECOVERING 甚至 ERROR 的概率就越大。

2. 拿到ERROR告警后的第一轮排查

告警来了以后,第一件事不是上手改配置,而是把现场固定下来。状态这种东西稍纵即逝,重启了或者停掉组复制之后,很多线索就没有了。

2.1 先固定现场:成员视图加复制通道状态

登录任意一个还在线的成员,最好是 PRIMARY,执行这条 SQL:

SELECT member_id, member_host, member_port, member_state, member_role, member_version FROM performance_schema.replication_group_members;

你会看到类似下面的输出:PRIMARY 的状态是 ONLINE,另一个正常的 SEC 也在 ONLINE,唯独出问题的那台显示 ERROR 或者干脆从成员列表里“消失”了。先把这个结果保存下来,因为它记录了故障发生时整个集群的拓扑快照。

然后登录出问题的 SEC 节点本身,查它的复制通道状态。MGR 的复制通道名字很固定,叫group_replication_applier:

SELECT CHANNEL_NAME, SERVICE_STATE FROM performance_schema.replication_connection_status;

如果这条通道的 SERVICE_STATE 不是 ON,说明这个节点本地的组复制线程已经停了。这时候再看一眼最终一致性视图里的事务统计:

SELECT MEMBER_ID, COUNT_TRANSACTIONS_IN_QUEUE, COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE, COUNT_TRANSACTIONS_REMOTE_APPLIED FROM performance_schema.replication_group_member_stats;

重点关注COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE。如果这个值一直在涨,说明节点不是被网络踢出来的,而是自己回放跟不上,数据侧出问题的可能性更大。

2.2 分清两种ERROR:被踢出和主动退出

这一步非常关键,直接决定后续处理方向,但很少有人会专门去区分。

第一种是节点被其他成员驱逐。它的特征是:组内其他成员长时间等不到该节点的响应,于是把它从成员列表移出。SEC 节点自己的错误日志里会出现类似 "has been expelled" 的信息。这时候节点的 mysqld 通常还活着,只是组复制停掉了。只要网络恢复,重新启动组复制就能加组。

第二种是节点自己退出。典型场景是 applier 线程遇到不可回放的事务错误,组复制插件主动中止。这种情况下,错误详情会留在performance_schema.replication_applier_status_by_worker表里。如果直接 START GROUP_REPLICATION,大概率还是失败,因为底层数据已经处于“卡住”状态,不解决事务冲突,节点就加不回来。

要区分这两种,用这条 SQL 就够:

SELECT WORKER_ID, LAST_ERROR_NUMBER, LAST_ERROR_MESSAGE, LAST_ERROR_TIMESTAMP FROM performance_schema.replication_applier_status_by_worker WHERE CHANNEL_NAME = 'group_replication_applier';

如果返回结果不为空,说明是 applier 出了问题;如果全表都是空,那优先怀疑通信和驱逐。

2.3 顺着错误日志拉时间线

组复制的插件日志统一写在 MySQL 错误日志里。别用grep ERROR一把梭,那样抓出来的都是噪音,最好带上插件上下文过滤:

grep -inE "group_replication|GCS|replication" /var/log/mysql/mysql-error.log | tail -n 120

我开始排障时会重点抓这几个关键词:expelled、timeout、failed to connect、leave the group、applier。然后按照时间戳把事件串起来,比如:凌晨 1:58 网络断开,2:00 节点被其他成员驱逐,2:01 配置里的exit_state_action把实例切成了只读或直接关闭。时间线一拉出来,根因基本就缩小到两类:通信层问题或者数据层问题,后面的事就清晰了。

3. ERROR根因分类与对应处理

SEC 节点掉进 ERROR,原因五花八门,但归纳起来就是四类:通信类、数据回放类、恢复链路类、配置类。这里一个个拆开讲,每一种都有对应的判断方法和处理套路。

3.1 通信类故障:被误判离线的典型场景

通信问题是最常见的,也是最容易解决的。典型情况包括:SEC 节点所在机房网络闪断、防火墙或安全组策略变更、跨区域专线路由异常、备份任务把带宽占满导致心跳延迟。

排查时先在故障节点和 PRIMARY 之间测试两个端口的连通性,一个是 MySQL 服务端口(比如 3306),一个是组通信端口(根据group_replication_local_address配置,常见 33061):

nc -vz 192.168.1.10 3306 nc -vz 192.168.1.10 33061

如果只是瞬时抖动,等网络恢复后,在故障节点上重新启动组复制就能加组。注意,这里有个参数直接关系到误驱逐概率:group_replication_member_expel_timeout。它控制的是从成员被判定失败到实际驱逐之间的等待窗口。默认值对跨机房、经常有网络小抖动的环境来说偏紧张,我一般会结合网络质量调到 5 到 10 秒。别怕调大一点会拖慢故障切换,MGR 的检测本来就是秒级机制,设得太短反而容易造成误伤。

3.2 数据回放故障:applier卡死

组复制对表结构有硬性要求——所有被复制的表都必须是 InnoDB 存储引擎,并且必须有主键或非空唯一键,否则无法保证事务冲突检测。这是 SEC 节点回放失败最常见的根因之一。

applier 卡死的迹象很明显:SEC 节点状态长期停在 RECOVERING,COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE只增不减,replication_applier_status_by_worker里能看到具体的报错,比如某张表不存在、字段类型转换失败、字符集不一致,或者提示表没有主键。

处理这类问题有一个很重要的原则:先在主库上把源头修好,比如给表补上主键、修正字段定义,然后再处理 SEC 节点。组复制不像传统主从复制,不能随便用sql_slave_skip_counter跳过事务。强行跳过等于把事务的一致性讨论按在地上摩擦,后续认证阶段会出现更大更隐蔽的坑。我一开始也试过在主库修完表结构后,直接在 SEC 节点重启组复制,结果因为数据缺口已经存在,节点还是进不了 ONLINE。最后老老实实走了一遍备份恢复才干净。所以后来只要定位到 applier 类错误,我基本直接进入“重建节点”通道,不在这上面死磕。

3.3 恢复链路断裂:GTID差距过大

还有一种情况很有意思:SEC 节点本身没毛病,就是落后太多了。节点加入组时,如果本地gtid_executed和组内其他成员差距很大,它会自动向 donor 节点请求缺失的 binlog 进行补数据。但如果它落后的事务已经超过主库的 binlog 保留周期,donor 上找不到对应的日志,recovery 就会失败。节点会在 RECOVERING 状态上反复挣扎,最后落到 ERROR。

判断方法很简单,对比双方的 GTID 集合:

-- 在故障节点上 SELECT @@GLOBAL.gtid_executed; -- 在 PRIMARY 上 SELECT @@GLOBAL.gtid_executed;

两个集合的差,就是节点落后的缺口。接着去主库上看SHOW BINARY LOGS;,确认最早的 binlog 文件是否覆盖了这个缺口。如果已经 purge 了,就别指望增量补齐了。这个场景在“节点离线时间过长”的环境里特别常见,比如 SEC 节点因为维护停机了一周,期间主库 binlog 按保留策略被清理,等再开机想加组,就已经回不去了。我的实践结论是:只要 GTID 缺口大过 binlog 保留窗口,直接走克隆或备份恢复,性价比远高于手工补数。

3.4 配置类故障:不丢人但很常见

配置问题导致 ERROR 的案例,在群里问的也不少,而且经常是一些很低级的错误。我整理了一张高频清单:

问题现象处理
server_id 重复节点加组被拒绝修改 my.cnf 里的 server_id,确保全局唯一
server_uuid 重复加组时报错或组内冲突确认 auto.cnf,不能直接拷贝其他节点的数据目录
local_address 配错本地端口没监听,组内连不上检查 IP 和组通信端口,确认防火墙放行
group_seeds 写错节点找不到种子节点保证 seeds 里有当前在线的成员地址
start_on_boot=OFF重启后节点不自动加组建议开启并写入配置文件,配合进程守护
exit_state_action 默认 ABORT_SERVER被驱逐后 mysqld 直接关闭8.0.16+ 建议改成 READ_ONLY,避免实例消失

还有一个我特别想强调的点:很多同学组复制参数只用了SET GLOBAL临时设置,没写进my.cnf,结果节点一重启,参数全回到默认值,节点默默处于 OFFLINE。从组视角看,就好像这个节点又掉线了。如果用的是 8.0.16 及以上版本,一定要把group_replication_exit_state_action从默认的 ABORT_SERVER 改成 READ_ONLY,否则被驱逐的那一刻,实例会直接退出,排障时你看到的就不是 ERROR,而是连接不上。

4. 恢复实操:把ERROR节点拉回ONLINE

定位到根因之后,恢复就有套路了。我按从轻到重的顺序,把三种恢复方式都写出来,按实际情况选。

4.1 轻量恢复:直接重启组复制

如果判定是通信原因导致的误驱逐,且没有数据回放错误,直接在出问题的 SEC 节点本地执行:

STOP GROUP_REPLICATION; START GROUP_REPLICATION;

执行完马上查成员状态,观察节点是不是先进 RECOVERING、再变 ONLINE。如果一直停在 RECOVERING,多半是数据侧有问题,回到上一节去查 applier。这里提醒一句:不要在主库上随便执行 STOP GROUP_REPLICATION。单主模式下主库退出会触发成员重新选主,虽然 MGR 有自动切换机制,但主动把主库摘下来制造一次不是必要的切换,纯属给自己增加风险。除非你在变更窗口内专门做切换演练,否则别碰 PRIMARY。

4.2 补GTID差距后回归:谨慎使用

这个方法只适合差距很小、差异在很短时间内能补齐的场景。具体做法是:在 PRIMARY 上找到 SEC 节点缺失的 binlog 文件,用 mysqlbinlog 在线拉取并回放到 SEC 节点:

mysqlbinlog --read-from-remote-server --host=PRIMARY_IP --port=3306 \ --user=repl_user --password=xxx --start-position=xxx mysql-bin.000123 \ | mysql -h127.0.0.1 -P3306 -urepl_user -pxxx

回放完成后,确认 SEC 节点的gtid_executed包含了所有缺失事务,然后再执行 START GROUP_REPLICATION。这里我必须说一句大实话:手工回放 binlog 很容易破坏 GTID 的一致性,漏一个事务、重复一个事务,都会导致节点加入组的时候认证失败,甚至污染整个节点。组复制对事务顺序和 GTID 集合的完整性极其敏感。所以这种方法我只在缺失事务量非常小、并且能完全对照 binlog 内容时才敢用。常规生产环境,我建议直接走下一步的全量重建,不要再在这里赌运气。

4.3 全量重建SEC节点:推荐路径

这是最靠谱的恢复方式,也是官方文档里会建议的标准路径。拿 XtraBackup 备份一台健康节点,恢复到故障节点,再重新加入组。完整流程我拆成十步:

  1. 确认 donor 节点状态 ONLINE,备份用户有 RELOAD、PROCESS、SELECT 等权限;
  2. 在 donor 上执行备份并通过网络传送到目标节点:
xtrabackup --backup --stream=xbstream --target-dir=/tmp/mgr_backup \ --host=donor_host --user=backup_user --password=xxx | \ ssh target_host "xbstream -x -C /var/lib/mysql_recover"
  1. 在目标节点上停掉组复制和 mysqld:
mysql -h127.0.0.1 -e "STOP GROUP_REPLICATION"; systemctl stop mysql
  1. 清理原数据目录并解压备份,确保数据目录属主和权限是 mysql 用户;
  2. 检查auto.cnf里的 server_uuid 和my.cnf里的 server_id,不能和集群内其他节点冲突,这是备份恢复场景最容易翻车的一步;
  3. 把组复制相关参数写进 my.cnf:
server_id=13 group_replication_group_name="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" group_replication_start_on_boot=ON group_replication_local_address="192.168.1.12:33061" group_replication_group_seeds="192.168.1.10:33061,192.168.1.11:33061" group_replication_allowlist="192.168.1.0/24" group_replication_exit_state_action=READ_ONLY group_replication_member_expel_timeout=10
  1. 启动 mysqld;
  2. 执行START GROUP_REPLICATION;;
  3. 用 SQL 观察节点状态从 RECOVERING 变成 ONLINE;
  4. 对比 primary 和该节点上的关键表数据,确认同步正常。

如果你的集群是 MySQL 8.0.17 以上,而且网络条件允许,我更推荐用克隆插件,省掉手动备份的手续:

CLONE INSTANCE FROM 'repl_user'@'donor_host':3306 IDENTIFIED BY 'password';

CLONE 会自动把 donor 数据复制过来并重建数据目录,完成后 mysqld 自动重启。它不会覆盖目标实例自己的 server_uuid,uuid 冲突的概率小很多,但 server_id 还是要人工确认。克隆完成后,同样按第 6 步配置组复制参数再入组。

4.4 恢复过程中的避坑清单

这几条是我踩过或者见别人踩过之后总结出来的,值得贴在脑子里:

  • 不要在 PRIMARY 上擅自 STOP GROUP_REPLICATION,哪怕只是“试一下”,也可能触发不必要的切换。
  • RECOVERING 状态不是失败,别急着杀节点。看COUNT_TRANSACTIONS_IN_QUEUE是否在减少,只要在推进,就说明它还在自愈。
  • 千万别把group_replication_bootstrap_group=ON留在业务节点上。误重启一次,就可能同时出现两个 PRIMARY,脑裂事故就是这么来的。
  • 恢复完节点之后,顺手把 expel_timeout 和 exit_state_action 这些参数调整好,否则节点可能刚回来又被踢走。
  • 如果实例因为默认的 ABORT_SERVER 行为直接关闭了,不要慌,按 4.3 的流程重来,同时把 exit_state_action 改成 READ_ONLY。

5. 复盘与预防:把SEC节点ERROR的发生率打下来

处理完一次故障,如果不复盘,下次大概率还要用同样的姿势摔一跤。我现在的习惯是把每次 SEC 节点 ERROR 的根因和处置动作归档,同时把容易引发这类故障的环境因素和参数配置提前加固。

5.1 网络和主机层要盯的细节

组通信端口是最容易被遗忘的。很多环境防火墙只放行了 3306,忘了组内还要通过 33061 这类通信端口互相连接,结果节点加组就是超时。另外,备份大任务和业务高峰期叠加,网络带宽被占满,也会让心跳延迟明显上升。建议把备份计划和业务高峰错开。时区同步也别忘了,跨机房部署尤其要注意 NTP 配置。最后是磁盘,binlog 和 relay log 写满磁盘是 ERROR 的高发前奏,监控要提前到磁盘使用率接近 90% 时就告警,别等满了再去补救。

5.2 参数调优参考

把常用的一组生产参数整理成表,照着评估自己的环境就行:

参数推荐值说明
group_replication_member_expel_timeout5-15容忍瞬时网络抖动,防止误驱逐
group_replication_exit_state_actionREAD_ONLY掉出组后保持实例可读,便于进一步处理
group_replication_start_on_bootON配合守护脚本,避免重启后默默离线
group_replication_allowlist显式成员列表云环境安全组多变,显式放行更稳
group_replication_consistency按业务需求选择一致性要求越高,SEC 节点压力越大

大部分参数支持动态修改,但我还是强烈建议都写进配置文件。这样无论是重启还是克隆恢复,参数都不会丢。

5.3 告警和日常巡检不能只盯着PRIMARY

很多团队的监控只做了“主库是否存活”,SEC 节点不报警,等到切换时才发现 backup 已经坏了几个月。建议用一个小脚本,每隔 30 秒查一次performance_schema.replication_group_members,只要发现成员状态不是 ONLINE,就触发告警。同时监控replication_applier_status_by_worker,有非零错误立即通知。每季度至少做一次角色互换演练,在变更窗口内把 SEC 节点顶上去的过程提前跑熟。平时多演练,故障真正到来时才不会手忙脚乱。

这次 SEC 节点 ERROR 的分析和处理,如果只记住一句话,我的体会是:不要对着一个 ERROR 状态去猜,先看成员视图、再看 applier 报错、最后拉错误日志时间线,按顺序走,大部分情况十分钟内就能定位。每次恢复完,顺手把当次日志归档,把超时参数和 exit_state_action 重新过一遍。我自己踩过几次坑之后,现在一开口就知道该先查哪张性能表,SEC 节点出 ERROR 的频率也明显低了下来。希望这篇经验能派得上用场。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询