MySQL 主从复制是很多团队的高可用底线,但用过的人都清楚,“底线”和“天花板”是两回事。手动切换、从库延迟、丢数据,任何一个都够你在凌晨两点的电话里崩溃。我这次要聊的,是一套我在生产环境完整落地过的 MySQL MGR 高可用集群方案。MGR 全称 MySQL Group Replication,它不像传统主从那样靠异步 binlog 拉取,而是用组通信协议让集群内多个节点实时达成共识。这套集群搭建完成后,主节点挂了能自动选主,事务在多数派节点上确认后才返回成功,数据一致性、故障转移速度和运维体验,都比我之前用的异步复制好一个档次。如果你正在纠结主从复制怎么升级、MGR 怎么搭、搭完怎么维护,这篇内容应该能帮你省掉不少弯路。文章整体按“为什么选它—环境准备—参数拆解—完整搭建—故障演练—运维排查”的顺序来讲,尽量做到看完能直接照着动手。
1. 为什么我推荐 MGR:它到底比主从复制强在哪
1.1 传统主从复制和 MGR 的本质区别
传统主从复制,本质上还是“一个主库负责写,若干从库异步拉 binlog”。这种架构最大的问题是:主库把 binlog 写进本地文件,从库能拉到多少、什么时候拉到,主库根本不管。主库瞬间宕机时,binlog 可能还没来得及传到从库,这部分数据就永久丢了。即便用了半同步复制,也只是在主库等待一个从库确认,另一个从库仍然可能落后,故障切换后照样对不上数据。
MGR 的思路完全不一样。它把多个节点组成一个组,组内每个节点都维护同一份状态机,任何一个事务的提交都要经过组内多数派节点的确认。你可以把它理解成几个朋友一起记账,每一笔账不是一个人说了算,而是大家现场核对、多数人认可后才记到各自的账本上。这样任何一个节点挂了,其他节点手里已经握着完整的数据和提交顺序,新主能从同一份状态里继续提供服务,而不是盲猜哪里有数据。
从运维角度讲,主从复制最让人头疼的就是切换。我早年间做切换,基本流程是:先检查从库有没有缺事务,补完 binlog 再改指向,最后对业务喊一声“可以写入了”。整个过程二三十分钟是常事,操作错一步,线上就多一个事故。MGR 把选主、切换、成员状态管理都放进了内置协议里,主库掉线后,集群自动在剩余节点里选出新主,应用层通过路由层把写流量指过去就行。这个差距,用过一次就很难回头。
单主模式下的 MGR 还保留了“单点写入”的优势,这也是我坚持它在生产环境替代传统主从的重要原因。业务习惯了只往一个主库写,应用不用改复杂度很高的多写逻辑,却能获得自动故障转移和更高一致性。用一句话概括:等于把原来需要手工切换、手工补数据的活,全部交给 MysQL 内部协议去做了。
1.2 MGR 是怎么保证数据一致的:Paxos 与事务认证
很多朋友第一次接触 MGR,会误以为它就是“半同步复制加强版”,其实不是。MGR 的一致性保证来自两层机制:组通信层和事务认证层。
组通信这一层,MGR 基于 Paxos 变体协议(Group Communication System,简称 GCS)。所有节点之间通过组通信通道交换消息,同一组内消息有严格的全序关系:不管是哪个节点发起的事务提交,组内所有在线节点收到的消息顺序都是一致的。这个全序关系是共识机制提供的,不会出现“A 节点先看到事务 X,B 节点先看到事务 Y”这种分叉。这也是 MGR 无脑裂特性的基础。
事务认证层解决的是并发冲突问题。MGR 要求 binlog 使用 ROW 格式,并打开transaction_write_set_extraction=XXHASH64,这样每个事务在提交前会被拆成行级别的写集合(write set)。认证阶段会把这个写集合和组内其他正在认证的事务做交集检测,如果发现改写了同一行数据,后提交的事务会被回滚或等待。你可以把它看作一个分布式的“乐观锁检查”,确保每个节点最终执行的提交顺序完全一致,而不是靠传输排队硬排。
还有个容易忽略的细节:MGR 的分布式恢复通道,本质上是一条特殊的异步复制通道。新节点加入组后,会从现有节点拉取 GTID 范围内的数据,进行状态追赶。通道名分别是group_replication_recovery和group_replication_applier,前者负责建立基线,后者负责持续应用组内事务。排查问题时经常要在这两张视图里翻错误,这个后面会详细讲。
1.3 单主模式 vs 多主模式:生产环境怎么选
MGR 支持两种运行模式:单主(single-primary)和多主(multi-primary)。单主模式下,组内只有一个节点可写,其他节点都是只读副本,通过group_replication_single_primary_mode=ON开启。多主模式下,所有节点都能接受写请求,组内通过认证机制处理冲突。我用过一段时间多主后,最终把生产环境固定在了单主模式,原因很直接:多主太考验表设计和业务形态。
先说多主模式的硬性要求:所有表必须有主键,否则认证逻辑无法精确追踪写集合,事务容易产生不可控的冲突;跨节点高并发写同一行基本是被拒绝的,业务需要接受一定的“写冲突回滚率”。对大多数后端应用来说,把并发写拆到多个库节点,远不如把读写分离和主从切换做好来得实在。
单主模式虽然只有一个写入口,但它兼容原来所有依赖“单库单写”的最佳实践,ORM、事务、锁、自增主键都无需调整。再加上 MGR 本身会自动管理从节点的read_only和super_read_only,主节点挂掉后新主自动变成可写状态,应用侧只需要处理短暂的重连和重试。除非你的业务确实需要多活写入、且接受冲突处理成本,否则我建议一律选单主模式。
下表是我对三种方案的直观对比,适合做方案汇报时直接参考:
| 对比项 | 异步主从 | 半同步复制 | MGR 单主 |
|---|---|---|---|
| 同步机制 | binlog 异步拉取 | 等一个从库 ack | 组内多数派确认 |
| 自动选主 | 不支持 | 不支持 | 内置协议选主 |
| 脑裂风险 | 靠外部工具规避 | 靠外部工具规避 | 有法定人数机制保障 |
| 写能力 | 单点 | 单点 | 单点 |
| 一致性 | 可能丢数据 | 至少不丢已 ack 事务 | 组内强一致 |
| 运维复杂度 | 中 | 中 | 低 |
2. 环境准备:三台 Rocky Linux 9 服务器需要做什么
2.1 服务器与端口规划
MGR 对服务器数量的最低要求是三台,因为多数派需要组内至少三分之二节点在线才能正常选主。两台节点虽然也能搭建,但失去任何一台都无法选举,高可用形同虚设。我的环境是三台 Rocky Linux 9 虚拟机,配置是 4 核 8G,跑 MySQL 8.0 做业务联調和故障演练完全够用。
IP 规划如下:
| 节点 | IP | server_id | 角色 |
|---|---|---|---|
| mysql-1 | 192.168.1.71 | 1 | 初始主库 |
| mysql-2 | 192.168.1.72 | 2 | 从库 |
| mysql-3 | 192.168.1.73 | 3 | 从库 |
端口方面有两个需要提前放行:3306 是 MySQL 服务端口,业务连接和数据传输都走它;33061 是组通信端口,节点之间的 Paxos 消息走这里。MGR 分布式恢复阶段,新节点连接 donor 节点也是走 3306,所以这两个端口都必须互相可达。建议直接用静态 IP 和 hosts 解析,别用 DHCP,否则哪天节点地址变了,allowlist 里配置的 IP 会直接把节点挡在外面。
2.2 MySQL 8.0.36 二进制安装与初始化
这里我选择二进制包方式安装,原因很简单:生产环境不需要额外的发行版管理逻辑,版本和目录都自己控制。下载mysql-8.0.36-linux-glibc2.17-x86_64-minimal.tar.xz后解压到/usr/local/mysql,然后创建数据目录。以下步骤在三台节点上都要执行一遍,只是 IP 和 server_id 不同。
# 解压并建立软链接 cd /usr/local tar xf mysql-8.0.36-linux-glibc2.17-x86_64-minimal.tar.xz ln -s mysql-8.0.36-linux-glibc2.17-x86_64-minimal mysql # 创建用户和目录 groupadd mysql useradd -r -g mysql -s /bin/false mysql mkdir -p /data/mysql/data /data/mysql/binlog /data/mysql/relaylog chown -R mysql:mysql /data/mysql配置文件建议从一开始就写好最终版本,别先初始化再改配置重启,那样容易漏参数。我直接把整套 MGR 参数写进/etc/my.cnf,第一次初始化就用最终配置。要注意,MGR 插件此时还没安装,所以所有 group_replication 开头的参数都必须带loose-前缀,否则 MySQL 启动时识别不了这些参数会直接报错退出。完整配置下一章细讲,初始化命令只有一条:
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=mysql初始化完成后,临时密码在错误日志里,默认路径/var/log/mysql/error.log,用 root 登录后立即改掉。建议每台节点初始化完成后先启动一次,确认基础实例能正常运行,再继续往下做,这样排错范围会更小。
2.3 防火墙、SELinux 与时钟同步检查
环境准备里最容易忽略的坑,第一是防火墙,第二是 SELinux。很多兄弟搭建 MGR 卡在节点反复 RECOVERING,查了半天排错日志,最后发现是 33061 端口被拦了。Rocky Linux 9 上明确放行两个端口:
firewall-cmd --permanent --add-port=3306/tcp firewall-cmd --permanent --add-port=33061/tcp firewall-cmd --reloadSELinux 默认 enforcing 模式,也会拦截 MySQL 的网络访问。生产环境如果安全要求允许,建议先确认 MySQL 相关进程状态,再决定调整策略,不要盲目setenforce 0。还有一项基础检查是时间同步。MGR 的 Paxos 协议虽然对时钟偏差有一定容忍度,但偏差太大会导致消息乱序和验证异常。集群所有节点统一用同一 NTP 时钟源,偏差控制在几百毫秒以内最稳。最后用hostname检查主机名,方便后续在错误日志里快速定位节点身份。
3. MGR 参数配置逐行拆解:my.cnf 里的每个选项在管什么
3.1 三个节点的 my.cnf 完整示例
我直接把 mysql-1 的完整配置贴出来,后两个节点只需要改server_id、bind_address、group_replication_local_address和report_host这四个地方。这个配置我反复用过,可以直接抄作业。
[mysqld] user = mysql port = 3306 bind_address = 192.168.1.71 datadir = /data/mysql/data socket = /data/mysql/mysql.sock pid_file = /data/mysql/mysql.pid server_id = 1 # GTID 基础 gtid_mode = ON enforce_gtid_consistency = ON # binlog 配置 log_bin = /data/mysql/binlog/binlog binlog_format = ROW binlog_checksum = NONE log_slave_updates = ON # 事务写集 transaction_write_set_extraction = XXHASH64 # 中继日志 relay_log = /data/mysql/relaylog/relaylog # MGR 插件及参数,注意插件未安装前要带 loose- loose-group_replication_group_name = "35db1d86-3edb-11ef-93a5-00163e043d45" loose-group_replication_start_on_boot = OFF loose-group_replication_local_address = "192.168.1.71:33061" loose-group_replication_group_seeds = "192.168.1.71:33061,192.168.1.72:33061,192.168.1.73:33061" loose-group_replication_bootstrap_group = OFF loose-group_replication_single_primary_mode = ON loose-group_replication_enforce_update_everywhere_checks = OFF loose-group_replication_ip_allowlist = "192.168.1.71,192.168.1.72,192.168.1.73,127.0.0.1" # 刷盘策略,配合 MGR 保证持久性 innodb_flush_log_at_trx_commit = 1 sync_binlog = 1group_replication_group_name是一个固定的 UUID,三个节点必须完全相同。可以直接用uuidgen生成一个,然后写进所有节点的配置文件里。这里用35db1d86-...只是示例,实际操作时一定用自己生成的 UUID,否则多套集群对外共享同一个组名,会出现互相拉数据的问题。
3.2 GTID、binlog 与事务写集参数解读
配置里的 GTID 参数是整个 MGR 的数据基线。gtid_mode=ON和enforce_gtid_consistency=ON是 MGR 的硬性前置条件,没有 GTID,节点之间无法准确描述“我这边都执行了哪些事务”,分布式恢复也无从谈起。binlog_format=ROW是必须的,MGR 需要靠行级别的前镜像和后镜像来生成 write set,statement 格式做不到,而binlog_checksum=NONE是从早期版本沿用下来的兼容性要求,主要防止认证阶段信息校验不一致。
transaction_write_set_extraction=XXHASH64这句很关键,它决定了事务的写集合怎么生成。XXHASH64 是一个哈希算法,MySQL 用它把每行主键标识转成固定长度的哈希值。有了这些哈希值,认证阶段才能判断两个事务是否改写了同一行。这个参数默认就是 XXHASH64,但我建议还是显式写出来,给别人接手配置时一眼能看到。
log_slave_updates的作用是让从节点把应用过来的事务也记录进自己的 binlog,这样任何一个节点都能作为其他节点的数据源。MGR 的分布式恢复场景下,新节点可能从任意一个在线节点拉数据,不开这个参数,副节点手里没有完整 binlog,恢复就会卡住。
3.3 节点间配置一致性:最容易踩的启动坑
MGR 对节点间配置一致性要求非常高,很多参数不一致,节点虽然能启动,但加入组的时候会以各种姿势失败。我踩得最深的一个坑是lower_case_table_names。这个参数在 Linux 上默认是 0,表名区分大小写,但如果你某个节点手滑改成 1,那么这个节点上产生的事务写集合,在别的节点上可能表都找不到,复制线程直接中断。
还有character_set_server和collation_server,建议三个节点保持一致。虽然 MySQL 会在 binlog 里带上字符集相关信息,但核对时不一致很容易造成隐性数据问题。最稳妥的做法是,写一份标准 my.cnf,针对每台节点只替换 server_id、IP 等少数动态项,其他全部一样。每台节点用mysqld --verbose --help | grep lower_case_table_names快速核对一遍,再启动实例。
关键提醒:group_replication_start_on_boot在生产环境建议保持OFF。你可能会想“我这节点重启后自动加入组不是更好吗”,但在故障演练里我遇到过:节点宕机恢复后,组内数据差距还没追赶完就自动加入,结果又触发下一次故障。手动控制首次加入时机,比让系统自作主张更可控。这个后续做成 systemd 服务时也可以配合脚本处理。
4. 从空库到三节点 MGR 集群:完整搭建实录
4.1 安装组复制插件并创建分布式恢复账号
三台节点都初始化完成、基础实例能正常启动后,就可以开始装插件了。分别在每台节点上执行:
INSTALL PLUGIN group_replication SONAME 'group_replication.so'; SHOW PLUGINS;看到group_replication | ACTIVE | GROUP REPLICATION | group_replication.so | GPL这一行就算装成功了。接下来创建分布式恢复账号,这个账号是节点之间互拉数据的身份凭证,三台节点都必须有,名字可以随意但要保持一致:
SET SQL_LOG_BIN=0; CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'ReplPass#2024'; GRANT REPLICATION SLAVE, BACKUP_ADMIN, GROUP_REPLICATION_STREAM ON *.* TO 'repl'@'%'; SET SQL_LOG_BIN=1;这里有两个细节值得多说一句。第一,SET SQL_LOG_BIN=0会临时关闭当前会话的 binlog 记录,避免建用户、授权这些操作被复制到组内其他节点。因为其他节点自己也建了同样的账号,这些语句不需要再复制一遍,不然会出现重复用户报错。第二,我显式用了mysql_native_password,主要是为了规避caching_sha2_password在分布式恢复阶段“公钥获取”那道坎。如果团队安全规范要求必须用默认插件,也可以在 my.cnf 中加loose-group_replication_recovery_get_public_key=ON,但我实测下来,指定 native 密码最省事。
4.2 引导第一个节点:bootstrap 的正确姿势
集群刚起步时没有现成的组视图,第一步必须先引导出第一个节点。这个操作只在 mysql-1 上执行一次,千万不能三台节点同时执行。引导时用bootstrap_group变量临时打开“初始化新组”的开关:
SET GLOBAL group_replication_bootstrap_group = ON; START GROUP_REPLICATION; SET GLOBAL group_replication_bootstrap_group = OFF;三句按顺序执行完,第一节点就已经在组内了。查一下状态确认 ONLINE:
SELECT * FROM performance_schema.replication_group_members;此时你应该看到一行记录,成员状态是 ONLINE,角色是 PRIMARY。这里特别强调一下bootstrap_group的开关:如果三台节点都去执行引导,等于同时创建了三个不同的组,虽然 IP 端口都一样,但组视图和 GTID 集合完全不一致,后续互相加入必出问题。我曾在测试环境手滑把第二个节点也引导了一次,结果两个“各自为政”的组,数据对不上,最后全部重装才恢复。
4.3 教另外两个节点入组并验证 ONLINE
第一个节点引导完成后,mysql-2 和 mysql-3 不需要再做 bootstrap,直接执行START GROUP_REPLICATION;即可。这两个节点会根据group_replication_group_seeds里配置的地址列表,找到已存在的组,然后从组内成员拉取数据追赶状态。
-- mysql-2 和 mysql-3 上分别执行 START GROUP_REPLICATION;执行完立即查看成员状态:
SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members;刚执行完的节点很可能出现在 RECOVERING 状态,这是正常的,它正在进行分布式恢复。数据量小的话,几秒钟内就变 ONLINE;数据量大的话,RECOVERING 会持续一段时间,此时可以通过下面这条语句观察恢复进度:
SELECT CHANNEL_NAME, SERVICE_STATE, LAST_APPLIED_TRANSACTION, LAST_ERROR_MESSAGE FROM performance_schema.replication_connection_status WHERE CHANNEL_NAME = 'group_replication_recovery';三个节点全部 ONLINE 后,再确认一下主库位置:
SHOW STATUS LIKE 'group_replication_primary_member';输出结果里的 UUID 对应 mysql-1 节点的 server_uuid。单主模式下也只有这个 UUID 所在的节点可以写入,其他节点会自动进入只读状态,这个由组复制自动管理,不需要手工设置read_only。
5. 故障转移实测与节点扩容/缩容
5.1 主库进程被 kill 后发生了什么
集群状态正常后,我做了最经典的故障演练:直接 kill 掉 mysql-1 的 mysqld 进程,模拟主库突然崩溃。
kill -9 <mysql-1 的 mysqld 进程号>随后在 mysql-2 上持续观察replication_group_members,整个过程大致分三个阶段:
- 阶段一:mysql-1 的状态变成 UNREACHABLE,其他两个节点仍然 ONLINE。
- 阶段二:经过短暂的检测和 Paxos 协商,mysql-1 被逐出组视图,剩下两个节点重新收敛。
- 阶段三:其中一个节点被选为新主,角色从 SECONDARY 变为 PRIMARY。
整个过程耗时大约几十秒,从应用视角看,就是写操作在这段时间内会报错或超时,之后恢复正常。这就是 MGR 的“故障转移窗口”,比传统手工切换动辄二三十分钟,体验完全是两个级别。
mysql-1 恢复服务后,先启动 mysqld,再手动执行START GROUP_REPLICATION;让它重新加入。因为我配置了group_replication_start_on_boot=OFF,它不会自动入组,这样能避免数据没追上就强行上线的尴尬。如果你发现恢复后的节点重新加入后卡在 RECOVERING,优先查它落后了多少数据,必要时候用备份对齐,具体做法见下一小节。
5.2 新节点加入:全量备份对齐 GTID 才是正确做法
测试环境数据量小,新节点直接START GROUP_REPLICATION就能靠 binlog 追赶完成。生产环境不一样,我见过一次新节点要补几个 G 的 binlog,补到一半主库 binlog 被定期清理线程 purge 掉了,分布式恢复立刻断掉。所以生产环境扩新节点的标准做法是:先用全量备份对齐基线,再让新节点入组追平增量。
具体步骤是,在现有任意在线节点上安装并执行 Percona XtraBackup:
xtrabackup --backup --target-dir=/backup/mgr-base --host=192.168.1.71 --user=backup --password=xxx xtrabackup --prepare --target-dir=/backup/mgr-base把/backup/mgr-base整体拷贝到新节点的数据目录,启动 MySQL。此时新节点的数据快照已经包含全量数据,但不包含快照之后产生的事务。好在 MGR 会基于 GTID 自动计算差异,业务压力不大的情况下,只需再拉取少量 binlog 就能进入 ONLINE 状态。如果差距依然太大,donor 节点的 binlog 又已经清理过,那就只能在业务低峰期暂停写入时做一个不中断服务的备份方案,或者临时调大 binlog 保留时长再操作。
5.3 缩容与节点的优雅退出
缩容比扩容简单。在要下线的节点上执行:
STOP GROUP_REPLICATION;执行成功后再查成员视图,该节点会变成 OFFLINE,随后自动从组视图里移除。这种是优雅退出,节点还会保留自己的数据,之后如果想重新加入,直接START GROUP_REPLICATION即可。
如果是节点异常宕机、网络断开,组内会在超时后自动驱逐故障成员,不需要手工干预。唯一要注意的是,不要跳过“法定人数”保护机制。比如三节点组,如果一台下线,剩下的两台还能继续工作;如果两台同时下线,剩下的一台不足以构成多数派,整个组会进入只读不可写状态,直到恢复两台以上节点。这是 MGR 防脑裂的设计,不是故障,强制改force_members强行拉起组是极其危险的操作,不到万不得已不要碰。
6. 高频故障排查与性能调优速查表
6.1 我实际遇到过的 5 个刁钻问题
排错经验比搭建过程更值钱,这部分我挑了自己线上反复出现的问题做成速查表:
| 症状 | 可能原因 | 处理办法 |
|---|---|---|
| 新节点一直 RECOVERING | 分布式恢复通道连接失败,或 binlog 被清理 | 查replication_connection_status的 LAST_ERROR;生产环境先全量备份对齐 |
| 节点加入时报 3096/3097 错误 | IP allowlist 未包含节点地址,或 33061 不通 | 核对group_replication_ip_allowlist,放行端口 |
| 复制中断,错误是表找不到 | 某节点用了不同lower_case_table_names或字符集 | 三节点配置一致后重建对应节点 |
| 加入时密码认证失败 | caching_sha2_password 公钥交换问题 | 创建用户时指定 mysql_native_password,或启用 get_public_key |
| 主库故障后迟迟不切换 | 网络分区导致剩余节点凑不齐多数派 | 确认网络质量,必须保证至少两个节点互通 |
其中第一个问题最隐蔽。我在一次扩容时看到新节点始终 RECOVERING,以为又是什么网络问题,查last_error_message才发现是 donor 节点 binlog 已经被日志清理任务删掉了。从此我给自己定了个规矩:生产环境新节点一律不留悬念,直接全量备份对齐,别赌 binlog 还留着。
6.2 日常监控命令与指标速查
MGR 的日常巡检,我的习惯是盯三块内容:成员状态、主节点身份、应用线程延迟。常用命令如下:
-- 成员状态与角色 SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members; -- 当前主节点 SHOW STATUS LIKE 'group_replication_primary_member'; -- 事务认证和应用统计 SELECT * FROM performance_schema.replication_group_member_stats\Greplication_group_member_stats里重点看COUNT_TRANSACTIONS_IN_QUEUE和COUNT_CONFLICTS_DETECTED,前者表示待排队事务数,后者表示检测到的写冲突次数。COUNT_CONFLICTS_DETECTED在多主模式下尤其重要,如果这个值持续增长,说明业务写冲突频发,得从表设计和路由策略上做调整。单主模式下这个值一般接近 0,如果也出现增长,说明有节点可能被错误地接了写请求。
监控指标方面,Prometheus + mysqld_exporter 里mysql_group_replication_member_state指标可以直接告警。我也习惯把group_replication_primary_member的变化记录到日志表里,方便事后复盘切换历史。
6.3 让 MGR 跑得更好的几个参数
MGR 默认配置是偏保守的,上线后做一轮调优非常有必要。我调整比较有效的是下面这几个:
# 并行应用线程数,默认才 1 loose-group_replication_applier_threads = 4 # 写集依赖跟踪,提升并行复制效率 binlog_transaction_dependency_tracking = WRITESET # 8.0 里建议把事务排队上限调高一些,尤其是高峰期 loose-group_replication_transaction_size_limit = 134217728group_replication_applier_threads决定的是应用组内事务时的并行度,默认值是 1,意味着所有同步事务在从库上串行应用。把文档库中的参数从 1 调到 4,对高并发小事务场景有明显的提升效果,但也要结合实际 CPU 核数,不要盲目调大,机器负载升高反而拖慢延迟。
binlog_transaction_dependency_tracking改成 WRITESET 后,并行判断从“提交顺序”变成“写集合是否有交集”,两道事务只要没写同一行数据就能并行应用,对 MGR 这种多写节点的应用场景特别友好。再加上 MGR 本身的事务认证机制,这套组合下来,从节点追数据的节奏能快不少。
另外,事务大小也需要控制。MGR 每个事务要在组内广播,事务过大不仅占用网络带宽,认证阶段处理 write set 的时间也会显著上升。官方默认单事务上限较大,但我的实操建议是业务侧把超过 100MB 的批量任务拆成小块提交,对稳定性和吞吐都有帮助。网络延迟上,MGR 对 RTT 非常敏感,跨城域网搭 MGR 会很痛苦,同机房千兆内网下延迟基本可以忽略,这也是规划架构时就要想清楚的事。
7. 写在最后:三个能帮你少熬夜的好习惯
MGR 这套集群从搭建到现在,给我最深的感受不是“功能多强”,而是“省心”。它把异步复制时代最折磨人的手动切换、补数据、脑裂判断全部收进了协议层。但省心不等于可以放飞,运维习惯不到位,照样会半夜爬起来处理事故。
第一个习惯是配置一致性巡检。我每次变更 MGR 节点的 my.cnf 前,都会用mysqld --verbose --help导出各节点的关键参数做 diff,特别是lower_case_table_names、character_set_server、collation_server这种容易漏的。不一致的节点,加入组的那一刻就是雷爆的时刻。
第二个习惯是记录“组身份信息”。MGR 的组名 UUID、恢复账号、密码、各节点 server_uuid,这些信息平时不起眼,等节点全挂要重新引导时就傻眼了。建议写进团队密码本和初始化文档里,别只存在某个人脑子里。
第三个习惯是定期做故障演练。不要等到真宕机了才发现应用层不会重试、路由层不会切主。我每个月会挑一个业务低峰期 kill 一次主库,观察切换耗时和应用报错情况,整个过程记录下来,后面优化起来有据可依。这套 MGR 集群搭建完成后,我还顺手把应用层的连接池探活、故障重试逻辑一起补齐了,真正做到“主库挂了,业务无感切换”,这大概才是高可用的真正意义所在。