☰
MySQL MGR单主高可用集群部署实战与故障排查
2026/10/28 3:34:35 网站建设 项目流程

上周又有人问我 MGR 的事,说测试环境跑通了,一上生产就各种节点加不进去、自动退出、主库切完业务直接报错。这套路我太熟了。MySQL MGR(MySQL Group Replication,组复制)单主高可用集群部署这条路我自己走过很多遍,从最初照着官方文档一行行敲命令,到后来看见报错码大概能猜出是哪一层出的问题,中间交的学费不算少。这篇文章想干的事很简单:把散落在手册、社区帖子和深夜调试记录里的东西,按一条完整的部署链路重新串一遍,让你照着做能跑起来,出了问题知道往哪儿看。

MGR 说白了就是 MySQL 官方自带的一套基于多数派投票的复制机制,节点之间互相通信、互相确认事务,多数派确认了才算提交。它解决的是传统一主多从那套方案里最难受的几件事:主库挂了谁来接、切换时数据会不会丢、脑裂怎么防。单主模式指的是集群里同一时刻只有一个节点可写,其余节点只读,主库故障时由组内成员自动选出新的主库。这套东西适合谁?适合那些业务体量在单机 MySQL 能扛住的范围内、又实在不能接受几分钟以上停机、同时不想引入太复杂外部组件的团队。如果你正被 MHA 的切换脚本折磨,或者预算买不起商业数据库,那这篇文章基本就是给你写的。

1. 方案选型:MGR 单主到底适合什么样的场景

动手之前先把选型这件事聊透,比上来就敲命令重要得多。我见过太多团队是因为"听说 MGR 是高可用方案"就直接上了,结果架构跟业务特征完全不匹配,后期运维成本比原来还高。

1.1 老式主从加切换脚本,真正的问题在哪

传统的做法是一主两从,加一个 Keepalived 做 VIP 漂移,或者上 MHA、 orchestrator 这类外部切换工具。这套组合在上一个十年里确实是主流,但它有几个绕不过去的硬伤。

第一个是数据丢失。异步复制下,主库把 binlog 发给从库就返回客户端成功,从库可能还没收到。主库这时候宕机,从库上就缺了最后那部分事务。你可能会说半同步能解决,半同步确实能减少丢数据的窗口,但它带来的是主库提交延迟上升,而且半同步在超时后会退化回异步,很多人根本没注意这个降级行为。

第二个是脑裂。Keepalived 靠心跳判断对端存活,网络抖动一下就可能误判,两个节点同时认为自己是主,VIP 飘来飘去,业务写两边,数据直接分裂。事后修数据的过程极其痛苦,我见过一次修了三天的。

第三个是切换逻辑的复杂度。什么时候切、切到哪个从库、切换后其他从库怎么重新指向新主、GTID 差集怎么补,每一个环节都要写脚本处理,脚本本身还可能出 bug。MGR 把这些东西全部收进内核里了,事务提交需要多数派确认,天然不存在脑裂窗口,主库失效由协议自己选新主,不需要任何外部脚本参与决策。

1.2 单主模式和多主模式的取舍逻辑

MGR 支持两种模式,单主和多主。多主模式下每个节点都能接受写入,听起来很美好,实际用起来坑不少。

多主的核心问题是冲突检测。每个事务提交前都要把写集(write set)广播给所有节点做认证,判断有没有跟别的事务冲突。如果两个节点同时更新同一行,后提交的那个会被回滚,客户端收到的是死锁或者回滚错误。业务侧如果不做重试处理,就会看到莫名其妙的失败。而且热点行更新在电商、计费这类场景里非常常见,多主模式下冲突概率会急剧上升,实际吞吐反而不如单主。

另一个问题是多主对自增主键、外键、触发器的限制,官方文档里列了一长串,配起来非常别扭。单主模式下这些限制基本都不存在,因为只有一个节点在写,冲突检测几乎不会被触发,认证流程的开销也低得多。

我的建议很直接:除非你有明确的写扩展需求,并且已经做好了业务侧的重试和分片设计,否则一律上单主。单主扛不住的写入量,靠加节点是解决不了的,那得上分库分表,那是另一个维度的工程。单主模式的写能力上限基本就是单机 MySQL 的上限,能扛住多少取决于你的硬件和 SQL 质量,MGR 本身不会让它变慢太多,组播和认证带来的额外开销通常在 5% 到 15% 之间。

1.3 集群规模、网络时延与硬件的最低门槛

节点数量这块,MGR 用的是多数派算法,容错能力和节点数是这个关系:3 节点容忍 1 个故障,5 节点容忍 2 个。偶数节点没有任何意义,4 个节点的容错能力和 3 个一样,但多了一份复制开销。所以要么 3 要么 5,官方上限是 9 个。

大多数团队 3 节点就够了,成本可控,容错能力也够用。金融类或者对可用性要求极高的场景可以上 5 节点,但要注意节点越多,事务提交需要等待的确认也越多,延迟会上升。

网络这块是很多人忽略的。MGR 的成员之间要保持长连接,事务认证走的是组通信。官方建议成员之间的网络往返时延控制在 5ms 以内,超过这个值,认证队列容易堆积,流控会被触发,写入性能断崖式下跌。同机房部署基本没问题,同城双活要看实际链路质量,跨城基本别想,除非你能接受写入性能砍半。

带宽也要算一笔账。每个事务的写集都要广播给所有成员,粗略估算,网络出口带宽大约是写入量的 (节点数 - 1) 倍。如果业务写入峰值为 50MB/s,5 节点集群主库出口至少要 200MB/s,也就是 1.6Gbps,这个量级得上万兆网卡了。

硬件方面,磁盘的 fsync 性能直接影响提交延迟,因为 MGR 不能靠关闭innodb_flush_log_at_trx_commit来换性能,那样会在故障时丢数据。建议用 SSD,最好是 NVMe。CPU 核数决定了组复制线程和应用线程的并行度,4 核起步,8 核以上比较舒服。

提示:千万不要在已经有大量数据的单机 MySQL 上直接开 MGR,先做一次全量备份,然后在新集群上恢复。在存量实例上直接改配置启组复制,失败的姿势千奇百怪。

2. 部署前的环境准备:这一层做不好后面全是坑

部署失败的原因里,有相当一部分根本不是 MGR 本身的问题,而是主机名解析、时钟、防火墙这些基础层没弄好。这一层我建议花半小时认真过一遍,能省掉后面几小时的排查。

2.1 操作系统与网络层的四件事

第一件是主机名解析。MGR 的group_replication_local_address和group_replication_group_seeds用的是主机名加端口,如果解析不到或者解析不一致,节点之间根本连不上。最稳妥的做法是在每台机器的/etc/hosts里把所有节点的映射都写死,不要依赖 DNS,DNS 出问题的时候排查会让你怀疑人生。

# 三台机器上都要有这三行 192.168.10.11 db1 192.168.10.12 db2 192.168.10.13 db3

第二件是时间同步。MGR 本身不强制要求时钟完全一致,但慢查询日志、错误日志、binlog 的时间戳对不上,排查问题时会非常难受。用 chrony 或者 ntpd 都行,确保三台机器的时间偏差在毫秒级。

# chrony 配置示例,指向内网时间源 server ntp.internal iburst

第三件是防火墙。除了 MySQL 服务端口 3306,MGR 还需要一个专用的组通信端口,习惯上配成 33061。这个端口只在成员之间使用,不需要对业务网开放,但成员之间必须互通。很多人只开了 3306,然后看着ERROR 3096发呆。

第四件是文件和进程限制。MySQL 在高并发下会打开大量文件描述符,默认的 1024 肯定不够。

# /etc/security/limits.conf mysql soft nofile 65535 mysql hard nofile 65535 mysql soft nproc 65535 mysql hard nproc 65535

SELinux 这块,如果团队没有专门的安全策略维护能力,测试环境直接设成 permissive,生产环境按需配置策略。我遇到过好几次因为 SELinux 拦截导致数据目录初始化失败的情况,报错信息还特别隐晦。

2.2 参数预调:写在配置文件里比动态设置靠谱

MGR 相关的参数有一部分必须在配置文件里写好,因为实例启动时就要加载插件。动态SET GLOBAL只适合调优类参数,不适合初始化类参数。下面这份是我常用的配置模板,你可以直接抄,改一下路径和 IP 就行。

[mysqld] server_id = 1 # 每个节点必须不同 port = 3306 datadir = /data/mysql/3306/data socket = /data/mysql/3306/mysql.sock log_error = /data/mysql/3306/error.log pid_file = /data/mysql/3306/mysql.pid # ---------- 复制与 GTID ---------- gtid_mode = ON enforce_gtid_consistency = ON binlog_format = ROW binlog_checksum = NONE log_bin = /data/mysql/3306/binlog/mysql-bin binlog_expire_logs_seconds = 604800 sync_binlog = 1 # 8.0.26 之前叫 log_slave_updates,之后推荐用新名字 log_replica_updates = ON # ---------- 事务与刷盘 ---------- innodb_flush_log_at_trx_commit = 1 innodb_buffer_pool_size = 8G innodb_flush_method = O_DIRECT innodb_redo_log_capacity = 4G # 8.0.30 及以上;更早版本用 innodb_log_file_size # ---------- MGR ---------- plugin_load_add = 'group_replication.so' plugin_load_add = 'mysql_clone.so' group_replication_group_name = "8f3c1a76-2b4d-11ef-9c8a-005056a1b2c3" group_replication_start_on_boot = OFF group_replication_local_address = "db1:33061" group_replication_group_seeds = "db1:33061,db2:33061,db3:33061" group_replication_bootstrap_group = OFF group_replication_single_primary_mode = ON group_replication_enforce_update_everywhere_checks = OFF group_replication_member_expel_timeout = 10 group_replication_autorejoin_tries = 3 group_replication_exit_state_action = READ_ONLY group_replication_consistency = BEFORE_ON_PRIMARY_FAILOVER # ---------- 其他 ---------- max_connections = 1000 character_set_server = utf8mb4

几个参数单独解释一下为什么这么配。

binlog_checksum = NONE是 MGR 的硬性要求。组复制的认证机制需要读取 binlog 事件的内容做校验,如果开启了 checksum,事件里会带上校验和字段,解析会出问题。这个参数不设对,节点加入时会直接报错。

group_replication_start_on_boot = OFF是故意设的。如果设成 ON,实例一启动就自动尝试加入组,但首次搭建时第一个节点需要用 bootstrap 方式启动,自动加入会导致启动失败或者组状态异常。建议整个集群稳定运行一段时间之后,再考虑改成 ON,让实例重启后能自动归队。

group_replication_member_expel_timeout = 10把默认的 5 秒延长到 10 秒,目的是容忍短时间的网络抖动。默认值太激进,一次 GC 停顿或者网络瞬断就可能把节点踢出去,然后触发一轮不必要的分布式恢复。

group_replication_autorejoin_tries = 3让被踢出的节点自动尝试重新加入三次,每次间隔 5 分钟。没有这个参数的话,节点被踢出后需要人工介入。

group_replication_consistency = BEFORE_ON_PRIMARY_FAILOVER是我比较推荐的一个值。它保证主库切换后,新主库上的读请求会等待积压事务应用完再返回,避免业务读到旧数据。设成AFTER一致性更强,但每个事务都要等所有节点确认,性能损失比较明显,除非业务对一致性要求极高,否则不建议。

group_replication_enforce_update_everywhere_checks在单主模式下必须是 OFF,这个参数是给多主模式用的。设错了节点根本起不来。

注意:group_replication_group_name必须是一个合法的 UUID 格式,可以用SELECT UUID()生成一个,整个集群用同一个值。千万不要用全零 UUID,也不要每个节点配不一样的值,后者会导致节点之间根本不认为自己在同一个组里。

2.3 账号、目录与端口规划表

部署之前把规划表拉出来,比边做边想要高效得多。下面这张表是我按三节点最小集群写的,你可以直接改 IP 用。

项目db1db2db3说明
内网 IP192.168.10.11192.168.10.12192.168.10.13同一网段,RTT 小于 5ms
server_id123全局唯一,不能重复
MySQL 端口330633063306业务访问端口
组通信端口330613306133061仅成员间通信
数据目录/data/mysql/3306/data同左同左建议独立磁盘
初始角色PRIMARYSECONDARYSECONDARY由 db1 引导

账号方面需要准备两个。一个是复制账号,MGR 内部用它建立成员间的复制通道;另一个是管理账号,用来执行 MGR 的管理命令比如切换主库。

-- 复制账号,三个节点都要建,且必须一致 CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl_Str0ng_Pwd'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; -- 克隆账号,用于节点重建时拉取全量数据 GRANT BACKUP_ADMIN, CLONE_ADMIN ON *.* TO 'repl'@'%'; -- 管理账号 CREATE USER 'mgr_admin'@'%' IDENTIFIED BY 'Mgr_Adm1n_Pwd'; GRANT SELECT, PROCESS, REPLICATION CLIENT ON *.* TO 'mgr_admin'@'%'; GRANT GROUP_REPLICATION_STREAM ON *.* TO 'mgr_admin'@'%';

GROUP_REPLICATION_STREAM这个权限容易被漏掉,它允许账号使用组复制专用的复制通道。

目录结构我一般按端口号分,这样一台机器跑多个实例时不会打架。数据目录、binlog 目录、错误日志目录分开建,binlog 单独放一块盘有助于排查 IO 问题。

3. 逐步实操:三节点单主集群从零搭起来

准备工作做完,接下来就是真刀真枪的部署。这段我按实际执行的顺序写,每一步都说明它在干什么,而不是单纯甩命令。

3.1 实例初始化与基础配置落地

三台机器上的操作完全一样,只有server_id和group_replication_local_address不同。先建目录,再初始化数据目录。

# 建目录 mkdir -p /data/mysql/3306/{data,binlog,log,tmp} chown -R mysql:mysql /data/mysql # 初始化实例,注意 --initialize-insecure 会生成空密码 root mysqld --defaults-file=/etc/my.cnf --initialize-insecure --user=mysql # 启动 mysqld_safe --defaults-file=/etc/my.cnf &

初始化完成后登录进去,先把 root 密码改掉,再建前面说的那几个账号。这里有个细节:复制账号必须在每个节点上单独创建,因为 MGR 加入组的时候不会同步账号信息,账号是实例级别的对象。很多人以为主库建了账号从库就有,然后卡在ERROR 1045上。

然后把group_replication_start_on_boot保持 OFF 的前提下,手工加载插件并检查。

-- 确认插件已加载 SELECT PLUGIN_NAME, PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME IN ('group_replication', 'clone'); -- 确认关键参数 SHOW VARIABLES LIKE 'group_replication%'; SHOW VARIABLES LIKE 'gtid_mode'; SHOW VARIABLES LIKE 'binlog_checksum';

gtid_mode必须是 ON,binlog_checksum必须是 NONE,这两个不满足,后面START GROUP_REPLICATION会直接失败。

3.2 复制通道与 MGR 插件的准备

MGR 在内部用的是标准的复制通道,但通道名不是默认的,而是固定为group_replication_recovery。这个通道负责新成员加入时的增量或全量数据同步。

在引导组之前,需要在每个节点上先配好这个通道的凭据,让它在需要的时候能自动连上捐赠者。

-- 停掉可能存在的组复制 STOP GROUP_REPLICATION; -- 配置恢复通道的凭据 CHANGE REPLICATION SOURCE TO SOURCE_USER='repl', SOURCE_PASSWORD='Repl_Str0ng_Pwd' FOR CHANNEL 'group_replication_recovery';

这里我用的是 MySQL 8.0.23 之后的新语法CHANGE REPLICATION SOURCE TO。老版本对应的是CHANGE MASTER TO ... FOR CHANNEL,如果版本更早,语法要相应调整。

另外,加入组之前要把本地 binlog 清干净。因为一个全新的节点如果带着本地事务的 GTID 集合加入,组会认为它有额外的数据,直接拒绝。

-- 8.0.21 及以上推荐用新语法 RESET BINARY LOGS AND GTIDS; -- 老版本用 RESET MASTER;

这一步只在首次搭建或者节点重建时做,已经在组里的节点千万别执行,那会清掉正在复制的 binlog。

还有一个前置检查是server_uuid必须唯一。克隆出来的实例、从虚拟机模板复制的机器,auto.cnf里的 UUID 可能是一样的,这种情况下节点加入会被拒绝,报的错还特别含糊。检查方法是:

SELECT @@server_uuid;

三台机器输出必须不一样,一样的话删掉datadir/auto.cnf重启,会自动重新生成。

3.3 引导首个节点:bootstrap 只做一次

这是整个部署过程中最容易出事的一步。引导(bootstrap)的意思是让第一个节点自己组建一个只有它自己的组,其他节点再往里加。关键在于:整个集群生命周期内,bootstrap 只能有一次。如果第二个节点也去 bootstrap,就会形成两个独立的组,两边都认为自己是正统,数据从此分裂。

在第一台机器 db1 上执行:

SET GLOBAL group_replication_bootstrap_group = ON; START GROUP_REPLICATION; SET GLOBAL group_replication_bootstrap_group = OFF;

三行必须按顺序执行,最后一行尤其不能忘。忘了关这个开关,之后实例重启时会再次尝试引导,那就真的分裂了。

验证一下状态:

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

正常情况下会看到只有一行,MEMBER_STATE为ONLINE,MEMBER_ROLE为PRIMARY。如果状态是RECOVERING那说明还在初始化,等几秒再看;如果直接是ERROR,去看错误日志,通常是参数没配对。

提示:如果执行完发现状态一直卡在RECOVERING,先看group_replication_local_address的端口能不能 telnet 通,很多时候是防火墙没放行 33061。

3.4 其余节点逐个加入与状态验证

db1 稳定运行后,去 db2 上执行加入。加入的时候要指定种子节点,这些种子只是用来发现组的,加入成功后种子列表就不重要了。

-- db2 上执行 START GROUP_REPLICATION;

因为配置文件里已经写好了group_replication_group_seeds,直接 START 就行。如果种子节点里有任何一个是可达的,就能成功加入。

加入过程分两个阶段:先做分布式恢复,把 db1 上已有的数据同步过来;然后进入认证阶段,开始接收新的事务。数据量大的话,恢复阶段会持续比较久,这段时间节点的状态是RECOVERING。

-- 在任意已加入的节点上查看 SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members;

db2 加入成功后,同样的步骤在 db3 上做一遍。全部完成后,三行都应该是ONLINE,其中一行是PRIMARY,两行是SECONDARY。

想看得更细一点,可以看成员统计表:

SELECT MEMBER_ID, COUNT_TRANSACTIONS_IN_QUEUE AS queue_len, COUNT_TRANSACTIONS_CHECKED AS checked, COUNT_CONFLICTS_DETECTED AS conflicts, TRANSACTIONS_WAITING_CERTIFICATION_QUEUE AS wait_cert FROM performance_schema.replication_group_member_stats;

COUNT_CONFLICTS_DETECTED在单主模式下应该一直是 0,一旦有增长说明配置有问题,比如不小心开了多主或者有节点被误认为可写。

3.5 客户端入口:把读写请求导向正确的地方

集群搭起来了,但业务怎么连?单主模式下只有主库能写,从库默认是super_read_only = ON,直连从库写会报错。这就需要一层路由。

最省事的方案是用 MySQL Router,它跟 MGR 配合得很好,能自动感知拓扑变化。配置里定义一个读写端口和一个只读端口,Router 会定期查询组信息,自动把流量指向正确的主库。

[routing:rw] bind_address = 0.0.0.0 bind_port = 6446 destinations = metadata-cache://mycluster/?role=PRIMARY routing_strategy = first-available [routing:ro] bind_address = 0.0.0.0 bind_port = 6447 destinations = metadata-cache://mycluster/?role=SECONDARY routing_strategy = round-robin

另一条路是用 ProxySQL,功能更复杂,能做读写分离、查询缓存、连接池,代价是配置和维护成本高一些。如果业务规模不大、只需要一个自动漂移的写入口,MySQL Router 就够了。

还有一种做法是应用层自己做,连接配置里配三个地址,启动时探测哪个是主库。这种做法我不太推荐,因为主库切换后应用需要重连,重连逻辑写不好会引发一连串超时。用中间件把这件事挡在外面会清爽很多。

注意:不管用哪种方案,一定要把主库地址从应用配置里彻底拿掉。我见过好几次集群本身切换成功了,但因为应用里硬编码了 db1 的 IP,业务还是挂了好几分钟。

4. 故障切换、节点维护与日常运维

集群跑起来只是开始,真正的考验在故障发生的那一刻。这部分我想把 MGR 在故障时的行为讲清楚,这样你看到日志和状态变化时不会慌。

4.1 主节点意外宕机时会发生什么

假设 db1 突然断电。组里剩下 db2 和 db3 两个节点,多数派是 2,刚好满足。它们会在几秒内检测到 db1 失联,触发 expelled 计时器,超时后把 db1 从成员列表里踢掉,然后发起新一轮选举。

选举的依据包括成员的group_replication_member_weight(默认都是 50,可以调高某个节点的值让它优先当选)、成员 UUID 的字典序等。整个过程通常在 10 到 30 秒内完成,具体取决于网络和member_expel_timeout的设置。

切换完成后,新的主库会自动将super_read_only关掉,开始接受写入。老的主库 db1 恢复后,group_replication_start_on_boot如果是 ON 会自动尝试归队,OFF 的话需要手工 START。

这里有个非常关键的坑:旧主库归队时,如果它本地还有未提交的事务或者 GTID 差集过大,分布式恢复会自动选择全量克隆(clone)方式,把整个数据目录覆盖掉。这是正常行为,但如果旧主库上有还没来得及归档的本地 binlog,就会被清掉。所以故障后如果想保留现场做分析,先把 binlog 备份出来再让节点归队。

如果想控制这个行为,可以调整group_replication_clone_threshold。默认值是 1000000,意思是差距超过 100 万个事务才走 clone。设得太小会导致频繁全量克隆,设得太大则增量恢复会非常慢。

4.2 计划内主节点切换的标准动作

计划内切换(比如主库要升级硬件、打补丁)比故障切换简单得多,用一条命令就能完成。

-- 先查出目标节点的 UUID SELECT MEMBER_ID, MEMBER_HOST FROM performance_schema.replication_group_members; -- 指定新的主库 SELECT group_replication_set_as_primary('目标节点UUID');

执行后,当前主库会先把所有未完成的事务推送完,然后降级为只读,目标节点升级为主库。整个过程是原子的,不会出现两个主库同时可写的窗口。

切换完成后要立刻验证:

SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members; SHOW VARIABLES LIKE 'super_read_only'; SHOW VARIABLES LIKE 'read_only';

新主库上的super_read_only和read_only都应该是 OFF,从库上都是 ON。

如果集群原本是多主模式,想改成单主,用SELECT group_replication_switch_to_single_primary_mode();,反过来用SELECT group_replication_switch_to_multi_primary_mode();。切换过程中会有短暂的不可写窗口,选在业务低峰做。

4.3 节点重新加入与克隆式分布式恢复

节点掉了之后想加回来,流程跟首次加入差不多,但多几个检查点。

先看本地数据目录是不是干净的。如果节点是因为磁盘故障重建的,数据目录是空的,直接 START 就会走全量恢复。如果节点只是网络抖动被踢出,本地数据还在,那要判断 GTID 差集有多大。

-- 被踢出的节点上查看 SELECT @@gtid_executed;

如果差距不大,MGR 会自动走增量恢复,从捐赠者那边拉 binlog 补上。这种情况下不需要任何额外操作,直接:

START GROUP_REPLICATION;

如果差距很大,或者说错误日志里提示Clone process has been initiated,那就是走全量克隆了。克隆过程会自动停实例、复制数据、重启实例,你只需要等。

有时候会遇到节点卡在ERROR状态起不来,这时候要先STOP GROUP_REPLICATION清理掉错误状态,再重新 START。如果还是不行,可能需要RESET BINARY LOGS AND GTIDS把本地数据清空,强制走全量克隆。

STOP GROUP_REPLICATION; RESET BINARY LOGS AND GTIDS; START GROUP_REPLICATION;

注意:执行RESET BINARY LOGS AND GTIDS之前一定要确认这个节点的数据可以被覆盖。如果这个节点是组里唯一还存活的节点,清空它等于清空整个集群。

4.4 必须盯住的监控指标

MGR 的监控重点和传统主从不太一样,除了常规的 QPS、连接数、慢查询,还有几个组复制特有的。

指标来源关键字段健康阈值异常含义
replication_group_membersMEMBER_STATE全部 ONLINE有 RECOVERING/ERROR 需关注
replication_group_membersMEMBER_ROLE恰好 1 个 PRIMARY多个 PRIMARY 说明模式错乱
replication_group_member_statsCOUNT_TRANSACTIONS_IN_QUEUE小于 1000认证队列堆积
replication_group_member_statsCOUNT_CONFLICTS_DETECTED恒为 0非 0 说明有写入冲突
replication_group_member_statsTRANSACTIONS_WAITING_CERTIFICATION_QUEUE小于 100认证压力过大
错误日志关键字 EXPELLED / UNREACHABLE无网络或节点异常

COUNT_TRANSACTIONS_IN_QUEUE这个值最能反映集群健康度。它表示认证队列里等待处理的事务数,正常应该是个位数。持续增长说明某个节点应用事务的速度跟不上,触发了流控。这时候要看该节点的 CPU 和磁盘 IO,往往是磁盘写不动了。

流控参数在group_replication_flow_control_*这一组里。默认的group_replication_flow_control_mode = QUOTA,根据队列长度动态调整。如果业务对延迟敏感,可以把阈值调高一点让流控晚点触发,但代价是节点之间的数据延迟会变大。

还有一个容易忽略的点是磁盘空间。binlog、redo log、克隆过程中的临时文件都会吃空间。克隆一个几百 G 的库,临时文件可能也要几百 G,空间不够会直接失败。建议数据目录所在分区保留 40% 以上的余量。

5. 常见报错与排查实录

这部分是我压箱底的东西,基本上你能遇到的错误都在这里了。

5.1 加入集群阶段的经典错误码

ERROR 3092 (HY000): The server is not configured properly to be an active member of the group,这个是最常见的。它本身是个笼统的错误,具体原因要看紧跟着的错误日志。我总结下来八成是这几种:server_id冲突、gtid_mode没开、binlog_checksum不是 NONE、log_replica_updates没开、group_replication_group_name不是合法 UUID。按顺序检查一遍基本能定位。

ERROR 3096 (HY000): The START GROUP_REPLICATION command failed,这个通常是网络问题。检查group_replication_local_address配置的端口有没有被监听,成员之间能不能互通。在任意节点上telnet db1 33061试一下,连不通就查防火墙。

ERROR 3098 (HY000): The server cannot join the group because it has a different group name,组名不一致。三台机器上这个参数必须完全一样,包括大小写和连字符。

ERROR 3099和ERROR 3100通常跟本地事务有关。节点本地有未提交的事务或者 GTID 集合不为空,需要RESET BINARY LOGS AND GTIDS。

还有一个报错码是 3092 的变种,提示This member has more executed transactions than those present in the group,意思是你这个节点的数据比组里还多,这几乎必然是之前 bootstrap 过或者从别的组迁移过来的实例。处理办法就是清空本地 GTID 后重新加入。

错误码大概率原因快速处置
3092参数不合规(server_id/gtid/checksum)逐项核对配置
3096组通信端口不通查防火墙与监听
3098group_name 不一致统一为同一 UUID
3099/3100本地残留事务或 GTID清 binlog 与 GTID 后重试
1045复制账号不存在或密码错每个节点单独建账号
2003种子节点地址解析失败检查 /etc/hosts

5.2 网络、认证与时钟类问题

账号问题我单独拎出来说,因为它太常见了。ERROR 1045 (28000): Access denied for user 'repl'@'db2',原因是复制账号只在主库建了,新加入的节点上没有这个账号。MGR 的账号是实例级别的,不随复制同步。解决办法就是每个节点都执行一遍建账号的 SQL,密码必须一致。

还有一种情况是账号建了但还是连不上,看错误信息里的主机名部分。MySQL 的账号是user@host形式,如果建的是'repl'@'192.168.10.%',而连接时用的是主机名,匹配不上就会拒绝。建议统一用'repl'@'%',或者把主机名和 IP 两种都建一遍。

时钟问题不会直接导致加入失败,但会让排查变得极其困难。有一次我排查一个认证队列堆积的问题,看了半天日志时间对不上,最后发现某台机器的时钟慢了两分钟,日志顺序完全乱了。定位问题前先date一下三台机器,能省很多事。

5.3 流控与性能抖动

业务侧反馈的现象通常是"偶尔有几秒钟写入特别慢",但查慢查询又查不出什么。这种情况八成是流控。

流控被触发的标志是错误日志里出现Flow control相关的信息,或者replication_group_member_stats里的COUNT_TRANSACTIONS_IN_QUEUE突然飙高然后回落。

根因通常是某个从库应用事务太慢,认证队列越积越多,主库为了不把从库甩太远,主动降速。要解决这个问题,得先弄清楚从库为什么慢。常见原因包括:从库的innodb_buffer_pool_size设得比主库小、从库磁盘性能差、从库上有大查询在抢资源。

还有一种情况是大事务。单个事务几百万行更新,在 MGR 里会被当成一个整体广播和认证,耗时非常长。这种大事务还会导致 binlog 传输时间过长,进而触发member_expel_timeout把节点踢出。所以业务上要尽量避免一次性大批量 update,改成按主键分批。

-- 分批更新的写法示例 UPDATE t_order SET status = 'DONE' WHERE status = 'PENDING' AND id > 0 ORDER BY id LIMIT 2000; -- 循环执行直到影响行数为 0

分片大小我一般建议 1000 到 5000 行一批,具体看单行大小。每批之间 sleep 几十毫秒,给复制留点喘息时间。

5.4 几条用血换来的实操心得

第一,永远不要在生产环境的第一个节点上直接 bootstrap。先把整个流程在测试环境跑两遍,包括模拟主库宕机、节点被踢出、节点重建这些场景,确认业务侧的重连逻辑没问题,再动生产。MGR 的第一次 bootstrap 如果搞错了,代价是重来一遍。

第二,记录 bootstrap 序列号。我在配置里会加一个注释,写明这个集群是什么时间、由哪个节点 bootstrap 的。听起来很傻,但当你面对一堆环境时,能快速确认哪个是原始组,比翻日志强。

第三,克隆阈值要根据数据量调。默认 100 万事务对小库来说是合适的,但对每天几百万事务的库,可能一天就触发一次全量克隆。这种场景下应该把group_replication_clone_threshold调大,让增量恢复承担更多工作。

第四,从库的硬件不要省。很多人觉得从库只读,配低一点没事。但 MGR 的从库要应用所有事务,硬件差了就会拖累整个集群。从库的规格至少跟主库持平吧,我是这么配的。

第五,定期做一次演练。挑个低峰期,主动 kill 掉主库进程,观察切换时间和业务表现。演练过的团队和没演练过的团队,真出事时的反应完全是两个物种。

第六,别把备份交给 MGR。MGR 保证的是节点间的数据一致和高可用,它不是备份方案。误删表这种逻辑错误,MGR 会忠实地把删除操作同步到所有节点。物理备份和逻辑备份该做还得做,而且要定期验证可恢复性。

第七,注意版本一致性。三台机器的 MySQL 版本必须完全一致,包括小版本号。滚动升级的时候要先升级从库,最后升级主库,每升级一个节点都要确认它成功归队并且状态是 ONLINE 再动下一个。

这套东西配下来,一个能用的单主高可用集群就跑起来了。剩下的事情就是慢慢观察、慢慢调,把参数调到跟你的业务特征匹配。每个团队的业务形态都不一样,别人那套跑得很稳的配置搬过来可能水土不服,还是得自己在真实负载下跑一段时间才能定型。

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

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

立即咨询