MySQL 8.0双主互为热备实战:Keepalived+GTID高可用架构
2026/9/19 2:54:05 网站建设 项目流程

MySQL 8.0 双主互为热备配置实战笔记

不少做运维和DBA的朋友应该都有过这种经历:业务量不大不小,上一套主从架构吧,主库一挂,从库要手动提主,还得改应用连接串,中间那几分钟业务直接不可用;上Keepalived加VIP那套吧,又怕脑裂,脚本写得不好反而弄出双写。我前阵子刚好给一套内部系统做完MySQL 8.0的双主互为热备,跑了大半年,期间还真实演练过两次主库宕机切换,感触挺深。

先说清楚一个概念:MySQL的双主(Master-Master)不是让你同时往两个库疯狂写业务的,那是自寻死路。双主互为热备的核心思路是“一主一备、互为主备”,平时只有一边承担写入,另一边作为热备节点同步数据,主库出问题时备库能在秒级内顶上来。这套方案好处很明显:不需要额外引入第三方高可用组件,纯靠MySQL自带的复制机制就能实现;两个节点都能当主库,切换时不用改架构;配合Keepalived之类的虚拟IP方案,应用端几乎无感知。

本文面向的是有一定MySQL基础、想在生产环境搭建双主热备的运维、DBA、后端开发同学。我会把从环境准备、参数配置、复制搭建、切换到故障演练的完整过程都写出来,顺便把我踩过的坑也一并交代清楚,希望能帮你少走点弯路。

1. 方案选型与整体思路

1.1 为什么选双主而不是主从

在决定做双主之前,我先把市面上常见的几种方案过了一遍:

  • 传统主从复制:一个主库一个从库,从库只读。主库挂掉后需要手动执行STOP SLAVE; RESET MASTER;之类操作把从库提升为新主库,整个过程依赖人工判断,业务中断时间不可控。
  • MHA(Master High Availability):能在数十秒内完成主从切换,但部署较复杂,需要额外节点做管理端,而且MySQL 8.0的兼容性需要仔细踩坑。
  • MGR(MySQL Group Replication):官方推荐的高可用方案,但要求所有节点都开启GTID,且对网络延迟比较敏感,很多老业务改造成本高。
  • 双主+Keepalived:两节点互为主备,通过虚拟IP对外提供服务,切换逻辑清晰,实现简单,运维成本低。

最终我选了双主方案,理由很简单:这套内部系统的数据量不算大(单库几十GB级别),并发写量也不高,主要诉求是“别因为单点故障让业务停摆”。双主架构在满足需求的前提下,比MHA和MGR都简单直接,排查问题也更方便。

1.2 双主方案的工作机制

双主互为热备,本质上是两个节点都开启binlog,并且互相把对方当作自己的主库来复制。

平时正常运行时:

Node A(Primary)------> Node B(Standby) <----------------------

Node A承担读写,Node B通过复制保持与Node A同步。当Node A宕机后,Keepalived检测到故障,虚拟IP漂移到Node B,Node B接管读写。业务恢复后,Node A经过修复重新上线,此时Node A反而变成了Node B的从库,数据追平后可以随时再次切换回去。

这里面有个非常关键的点:既然两个节点互为主备,那就会存在“两边同时写入”的可能性。一旦双写发生,binlog里的数据冲突轻则复制报错中断,重则导致数据错乱。所以必须从架构层面避免双写,最常用的手段就是Keepalived的虚拟IP——同一时间只有持有VIP的节点能对外提供服务,另一节点的MySQL虽然开着,但应用根本连不上它。另外还可以在数据库账号层面做限定,比如专门创建一个只允许从VIP来源访问的账号,最大程度上防止跳过高可用层面的直连写入。

1.3 这套方案的适用边界

把丑话说在前面:双主方案不是银弹。

架构上双主更适合“读写分离不明显、单点写入为主、但需要高可用”的场景。如果你的业务有非常大的并发写入、需要水平扩展,或者两个库处于不同数据中心需要跨地域容灾,那双主方案就不合适了。跨机房做双主,延迟会显著影响复制链路的实时性,一旦网络抖动,备库延迟可能飙到几秒甚至几十秒,这时候“热备”其实已经变成“温备”了。

所以做之前先想清楚自己的业务边界。我这次部署的范围在同一个机房的同一网段内,网络延迟低于1ms,这才敢放心用双主。

2. 环境准备与核心参数配置

2.1 服务器与系统环境

我这边用的两台机器配置如下:

项目Node ANode B
主机名db-master-01db-master-02
IP地址192.168.10.11192.168.10.12
虚拟IP192.168.10.10(Keepalived漂移)192.168.10.10
操作系统CentOS 7.9CentOS 7.9
MySQL版本8.0.368.0.36
硬件配置4核8G / SSD4核8G / SSD

两台机器清一色CentOS 7.9,MySQL用的是8.0.36社区版,二进制包安装。之所以不用yum源直接装,主要是为了统一版本和安装路径,后续好维护。生产环境强烈建议两台的MySQL版本保持一致,尤其是大版本一定要一致,8.0和5.7之间做复制虽然可行,但坑太多,实在没必要给自己添堵。

2.2 操作系统级优化

在安装MySQL之前,先对两台机器做同样的系统层面优化:

# 关闭防火墙和SELinux(内网环境可以这么做,外网环境建议放开特定端口) systemctl stop firewalld systemctl disable firewalld sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config # 调整系统文件句柄限制 cat >> /etc/security/limits.conf << EOF mysql soft nofile 65535 mysql hard nofile 65535 mysql soft nproc 65535 mysql hard nproc 65535 EOF # 关闭透明大页,减少内存分配带来的性能抖动 echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag

操作系统层面的优化其实经常被忽略,但影响很大。文件句柄限制不够,高并发下MySQL会报Too many open files;透明大页开启时,内存分配可能会让MySQL出现间歇性卡顿,这在复制场景下会表现为莫名其妙的“备库延迟抖动”。先把地基打稳,后面排查问题能少很多事。

2.3 MySQL 8.0 配置文件详解

MySQL 8.0的配置文件不像5.7那样在my.cnf里写skip-networking之类简单粗暴的参数就行,8.0默认启用了一些新特性,需要个性化调整的参数更多。两台机器除了server-idserver-uuid不同外,核心参数保持一致。

Node A的/etc/my.cnf关键配置:

[mysqld] # 基础设置 user = mysql port = 3306 basedir = /usr/local/mysql datadir = /data/mysql socket = /tmp/mysql.sock pid-file = /data/mysql/mysql.pid # 字符集与排序规则 character-set-server = utf8mb4 collation-server = utf8mb4_0900_ai_ci # InnoDB引擎设置 innodb_buffer_pool_size = 4G innodb_log_file_size = 512M innodb_flush_log_at_trx_commit = 1 innodb_flush_method = O_DIRECT # binlog设置 server-id = 11 log_bin = /data/mysql/logs/binlog binlog_format = ROW binlog_row_image = FULL expire_logs_days = 15 max_binlog_size = 512M sync_binlog = 1 # 复制设置 gtid_mode = ON enforce_gtid_consistency = ON log_replica_updates = ON skip_slave_start = OFF relay_log = /data/mysql/logs/relaylog relay_log_purge = ON

Node B只有两处不同:server-id = 12,另外MySQL会自动生成不同的server-uuid,这个不用手动干预。

几个参数值得展开说说:

binlog_format = ROW。MySQL 8.0默认就是ROW格式,这也意味着复制时备库执行的是“行级变更”,对于UPDATE、DELETE这类操作可以精确到具体受影响的行。ROW格式最大的好处是复制更安全,不会因为SQL上下文不一致出现数据偏差。但要注意,ROW格式下binlog会更大,尤其是大批量UPDATE时,每个受影响的行都会产生一条日志记录,所以binlog大小参数max_binlog_size尽量调大些,我这边设置的是512M。

sync_binlog = 1配合innodb_flush_log_at_trx_commit = 1,是数据安全要求比较高的标准配置。这意味着每次事务提交时,binlog和InnoDB redo log都要同步落盘,性能会有一定影响,但换来的是极其重要的保障——主库在任何时刻宕机,都不会丢已经提交事务的数据。对双主热备架构来说,这个配置尤其关键,因为备库就是靠binlog来同步的,binlog丢一条就意味着备库数据不完整。

gtid_mode = ON+enforce_gtid_consistency = ON,这是MySQL 5.6以后引入的GTID复制模式。以前的传统复制要手动指定MASTER_LOG_FILEMASTER_LOG_POS,一旦binlog做过清理或备库做过临时的跳过复制操作,这个位点就很容易搞错。GTID模式下,每个事务都有全局唯一的标识,复制自动通过GTID定位位点,主备切换后重新搭建复制变得异常简单。MySQL 8.0官方其实已经不太推荐传统复制方式了,新搭的环境直接上GTID,别犹豫。

2.4 初始化MySQL实例

MySQL 8.0的初始化命令和5.7不一样,5.7用的mysql_install_db已经废弃,8.0统一使用mysqld --initialize

# 创建数据目录和日志目录 mkdir -p /data/mysql/logs chown -R mysql:mysql /data/mysql # 初始化数据库实例 /usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql

这里有个细节:执行完初始化后,root用户的临时密码会写到错误日志里,需要先找出来:

grep 'temporary password' /data/mysql/mysql-error.log

然后用临时密码登录并立即修改密码:

mysql -uroot -p ALTER USER 'root'@'localhost' IDENTIFIED BY 'Your-Strong-Passwd-2024';

MySQL 8.0默认安装了validate_password组件,对密码强度要求比较严格,测试环境可以直接卸载,生产环境建议保留并设置至少12位含大小写字母、数字和特殊字符的强密码。

3. 复制账号创建与双主复制搭建

3.1 创建复制专用账号

MySQL 8.0的账号创建语句和5.7也有明显变化,没有5.7里的GRANT ... IDENTIFIED BY写法了,必须分两步走(先建用户,再授权):

在Node A上执行:

CREATE USER 'repl'@'192.168.10.%' IDENTIFIED WITH caching_sha2_password BY 'Repl-Str0ng-Passwd'; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'192.168.10.%'; FLUSH PRIVILEGES;

在Node B上同样执行一遍,账号相同。

这里有个坑必须提醒你:MySQL 8.0默认的认证插件是caching_sha2_password,不是5.7时代的mysql_native_password。用caching_sha2_password做复制连接时,首次连接需要传输明文密码,这要求主库和备库之间能走加密通道,或者明确开启get_public_key选项。如果搭建复制时报错Authentication plugin 'caching_sha2_password' cannot be loaded,或者连接后一直卡住,多半就是这个问题。

规避方式有两种,二选一:

方案一:建账号时强制使用mysql_native_password

CREATE USER 'repl'@'192.168.10.%' IDENTIFIED WITH mysql_native_password BY 'Repl-Str0ng-Passwd';

方案二:保持默认的caching_sha2_password,然后在备库执行CHANGE MASTER时加上公钥获取选项:

CHANGE MASTER TO ... , MASTER_PUBLIC_KEY_PATH = '/data/mysql/public_key.pem';

我实际生产环境用的方案一,虽然mysql_native_password是旧插件,但它兼容性最好,复制链路上少一个加密握手的过程,内网环境安全性已经足够。

3.2 主库备份数据并导入备库

因为是新搭建,两张库其实都是空库,但这个步骤还是要走一遍,因为你在生产环境操作的时候,大概率不是从零开始的。标准做法是用mysqldump全量导出主库数据,再导入备库。

Node A上执行全量备份:

/usr/local/mysql/bin/mysqldump \ --single-transaction \ --master-data=2 \ --all-databases \ --set-gtid-purged=ON \ > /tmp/full_backup_$(date +%F).sql

几个参数拆开讲:

--single-transaction利用InnoDB的MVCC机制,在导出过程中拿到一个一致性的快照,不会锁业务表。这个参数在5.7和8.0里都是InnoDB表全量备份的标配。

--master-data=2会在备份文件的头部注释里自动记录当前主库的binlog位点信息,虽然我们用GTID模式,但这个信息仍然有诊断参考价值。

--set-gtid-purged=ON这个参数在搭建GTID复制时非常关键。它会告诉备库:“这份备份数据里已经包含哪些GTID事务了,你从这些事务之后开始复制就行。”如果不加这个参数,导入备库后GTID集合是空的,备库会把导入的这堆数据也当作需要复制的事务,可能导致主键冲突。

然后把备份文件拷贝到Node B并导入:

scp /tmp/full_backup_*.sql 192.168.10.12:/tmp/ mysql -uroot -p < /tmp/full_backup_*.sql

导入完成后,Node B的数据就和Node A一致了。

3.3 双向复制链路的建立

现在开始搭双主复制。先在Node B上把Node A当作主库:

-- 在Node B上执行 STOP REPLICA; CHANGE REPLICATION SOURCE TO SOURCE_HOST = '192.168.10.11', SOURCE_PORT = 3306, SOURCE_USER = 'repl', SOURCE_PASSWORD = 'Repl-Str0ng-Passwd', SOURCE_AUTO_POSITION = 1; START REPLICA;

MySQL 8.0.23以后,CHANGE MASTER TO正式更名为CHANGE REPLICATION SOURCE TOSTART SLAVE更名为START REPLICA,老命令虽然还能用,但新版本会提示deprecated。写新的自动化脚本时直接用新语法,省得以后升级麻烦。

然后在Node A上把Node B当作它的主库:

-- 在Node A上执行 STOP REPLICA; CHANGE REPLICATION SOURCE TO SOURCE_HOST = '192.168.10.12', SOURCE_PORT = 3306, SOURCE_USER = 'repl', SOURCE_PASSWORD = 'Repl-Str0ng-Passwd', SOURCE_AUTO_POSITION = 1; START REPLICA;

注意:这两条链路的SOURCE_USER用的都是同一套复制账号,只不过Host方向相反。SOURCE_AUTO_POSITION = 1表示开启GTID自动位点定位,它比手动指定SOURCE_LOG_FILESOURCE_LOG_POS的好处在于,即使备库本地已经执行了一部分事务,它也能自动跳过,不会重复执行导致报错。

3.4 验证复制是否正常

在Node A上执行一条测试SQL:

CREATE DATABASE test_repl; USE test_repl; CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50)); INSERT INTO t1 (name) VALUES ('hello'), ('world');

然后到Node B上查一下:

SHOW DATABASES; SELECT * FROM test_repl.t1;

如果两条复制链路都正常,Node B上能看到test_repl库和t1表的数据。把名字反过来测试一遍——在Node B上建表插入数据,看Node A能否同步过来。因为这是双主,两个方向的复制都必须验证通过。

另外别忘了用SHOW REPLICA STATUS\G查看复制状态,重点看这几个字段:

Replica_IO_Running: Yes Replica_SQL_Running: Yes Seconds_Behind_Source: 0 Last_IO_Errno: 0 Last_SQL_Errno: 0

Replica_IO_RunningReplica_SQL_Running必须同时为YesSeconds_Behind_Source为0表示已经追平主库,没有延迟。Last_IO_Errno 和 Last_SQL_Errno 只要不是0,就要立刻去错误日志里查原因。

4. Keepalived 实现VIP自动漂移

4.1 Keepalived 的安装与基础配置

复制链路搭好只是第一步,双主还差最后一环——让应用能自动切换到存活节点。这里我用Keepalived管理一个虚拟IP(VIP),哪个节点MySQL正常,VIP就落在哪个节点上,应用连的是VIP而不是真实IP,数据库节点切换时应用完全无感知。

两台机器都安装Keepalived:

yum install -y keepalived

主节点(Node A)的/etc/keepalived/keepalived.conf配置:

global_defs { router_id LVS_DEVEL_11 script_user root enable_script_security } vrrp_script check_mysql { script "/etc/keepalived/check_mysql.sh" interval 3 timeout 3 fall 2 rise 2 } vrrp_instance VI_MYSQL { state MASTER interface ens192 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.10.10/24 dev ens192 label ens192:vip } track_script { check_mysql } notify_master /etc/keepalived/notify.sh MASTER notify_backup /etc/keepalived/notify.sh BACKUP notify_fault /etc/keepalived/notify.sh FAULT }

备节点(Node B)的配置基本一样,只有两处不同:

router_id LVS_DEVEL_12 state BACKUP priority 90

这里有个关键点:Keepalived自身通过statepriority来决定谁是主,平时Node A(MASTER)优先级100,Node B(BACKUP)优先级90。当主节点故障后,备节点通过VRRP协议感知不到主节点的心跳,自动把VIP抢过来。如果主节点恢复,它会发现自己的优先级更高,又会把VIP抢回去。但因为双主架构里两个节点MySQL数据是一致的,VIP来回切换并不会造成数据问题。

4.2 MySQL存活检测脚本

Keepalived不会天然知道MySQL是否健康,它只负责检测本机IP网络是否正常。所以需要写一个脚本,定时检查MySQL进程是否存活、端口是否能连通、是否能正常执行查询,三重校验缺一不可。

#!/bin/bash # /etc/keepalived/check_mysql.sh MYSQL_HOST="127.0.0.1" MYSQL_PORT="3306" MYSQL_USER="repl" MYSQL_PASSWORD="Repl-Str0ng-Passwd" mysqladmin -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PASSWORD} ping > /dev/null 2>&1 if [ $? -eq 0 ]; then exit 0 else exit 1 fi

脚本写完后记得加执行权限:

chmod +x /etc/keepalived/check_mysql.sh

在实际生产环境,可能还需要考虑一种情况:MySQL进程在,但数据库已经僵死,mysqladmin ping虽然能返回结果但查询卡住。更严格的做法是再写一个实际执行SQL查询的检测,比如SELECT 1,并设置超时时间。考虑到检测脚本每3秒跑一次,如果里面卡住5秒,反而会影响Keepalived自身的心跳,所以我这边mysqladmin ping已经够用。

4.3 切换时的业务恢复与收尾处理

Keepalived把VIP从宕机节点漂移到存活节点后,MySQL的数据一致性可能还差一点——因为宕机节点可能还有一部分binlog没来得及传给存活节点。这部分丢失的数据在“热备”场景下不可避免,属于主库宕机时的最小RPO(恢复点目标)窗口。

如果只是主机宕机,MySQL文件还在,但直接重新把宕机节点加回双主架构,要小心。常规操作是:

# 在故障节点上停MySQL systemctl stop mysqld # 检查MySQL数据文件是否完好 ls -l /data/mysql/ # 将故障节点以从库角色重新加入(它会自动通过GTID追平数据) # 不需要重新初始化,启动后复制会自动通过GTID接着同步 systemctl start mysqld

启动后,故障节点会自动连接存活节点,通过GTID把自己落后的数据补回来。补完之后,这台节点的数据就和存活节点一致了,双主架构恢复如初。

如果故障节点损坏严重,数据文件都坏了,重建流程就是把存活节点的数据重新导出一份,走一遍当初搭建双主的流程。

5. 故障切换演练实录

5.1 模拟主库挂掉

架构搭好之后必须演练。纸上谈兵没用,没真实切过几次,真出问题的时候手忙脚乱,反而容易二次事故。

第一次演练我模拟的是Node A宕机:

# 模拟硬件故障,直接把Node A的MySQL强制杀掉 kill -9 $(cat /data/mysql/mysql.pid)

杀掉进程后,立刻到Node B上看VIP是否漂移过来:

ip addr show ens192

正常情况下,192.168.10.10已经绑定到Node B的ens192网卡上。

然后在Node B上验证业务可用性:

mysql -h192.168.10.10 -uroot -p SELECT 1;

能查通就说明应用的连接已经被接管了。

5.2 应用写入测试

我在Node B上模拟业务写入:

USE test_repl; INSERT INTO t1 (name) VALUES ('node-b-after-failover'); SELECT * FROM t1;

数据写入成功。此时整个系统的状态是:

  • VIP在Node B上
  • 业务读写正常
  • Node A处于宕机状态,复制链路自动中断
  • 应用层面没有做过任何改动,连接串用的还是VIP

接着把Node A恢复:

systemctl start mysqld

Node A启动后,由于${server-id}和GTID的自动追踪,它会自动把自己在宕机期间落后的数据从Node B补回来。大约几秒后,检查复制状态:

SHOW REPLICA STATUS\G

此时Node A相对于Node B的角色是“备”,但它的数据已经追平。如果没有人力介入,VIP仍然在Node B上——因为Keepalived不会因为Node A恢复了就立刻把VIP切回去,这符合高可用的预期。只有当Node B也挂掉、或者我们手动把Node B的Keepalived停掉时,VIP才会再次漂移回Node A。

这个机制我需要特别说明一下:双主架构在故障恢复后有“主备角色互换”的现象。Node A挂了再回来,它会发现自己原来的复制链路已经被GTID自动接管,当前的实际主库是Node B。这时候如果想让Node A重新变回主库,只需要把VIP手动停掉再重新拉起,Keepalived的优先级机制就会让VIP回到Node A。MySQL层不需要做任何切换操作,因为两个节点的数据已经通过复制保持一致。

5.3 脑裂风险的思考

使用Keepalived做高可用,最怕的就是脑裂——两个节点都认为自己是主,都持有VIP,都在处理写入。这会导致两份数据同时变化,复制链路上必然出现主键冲突。

Keepalived防脑裂最有效的机制是VRRP的组播心跳。正常情况下Node A和Node B每1秒互相通信一次,节点只有在超过3个心跳周期(约3秒)没收到对方消息后才会尝试接管VIP。如果两台机器之间的网络闪断,两边都会以为自己成了孤主,从而同时绑定VIP。

降低脑裂风险我这边做了几件事:

  1. 保证两台机器在同一个二层网络,Keepalived的心跳走的是组播,跨三层需要特殊配置,我不建议搞复杂。
  2. 检测脚本里调用mysqladmin ping时,其实还会隐性验证一下本机MySQL状态,如果MySQL都挂了,即便Keepalived认为自己是MASTER,VIP绑上了业务也照样连不上,相当于自动降级。
  3. 在较新版本的Keepalived里,可以给vrrp_instance配nopreempt或者unicast_src_ip等参数来微调行为,但生产环境没特殊需求不建议动,默认行为经过大量验证是最稳的。

说实话,双主+Keepalived的方案在脑裂场景下做不到绝对无风险,但对大多数中小规模业务来说,这个性价比已经很高了。真要彻底解决脑裂,得上分布式共识协议(比如etcd)、或者用MGR这类强一致方案,那是另一个话题,本文不展开。

6. 常见问题与排查技巧

6.1 复制报错:主键冲突

双主复制最经典的坑就是主键冲突。比如业务通过VIP写入Node A,但同时有人直连Node B(绕过VIP)也插入了一条相同主键的数据,Node B的binlog把这条变更同步给Node A时,Node A发现主键已存在,复制链路就会卡住。

排查方式:

SHOW REPLICA STATUS\G

看到Last_SQL_Errno: 1062,同时Last_SQL_Error提示Duplicate entry。

处理方法分两步:

第一步,确认哪条SQL出错,去主库(写入源)查看这条数据是否有必要保留,如果确实是业务误操作,直接在“真正的主库”删除那条数据。

第二步,让复制跳过这个错误事务。GTID模式下比较标准的做法是:

STOP REPLICA; SET GTID_NEXT = 'xxx-xxx-xxx:123'; -- 这个GTID来自Last_SQL_Error里的报错信息 BEGIN; COMMIT; SET GTID_NEXT = AUTOMATIC; START REPLICA;

但你必须清楚:跳过事务是治标不治本。如果同一类问题反复出现,说明有人/有应用绕过VIP直连备库写数据,这才是根源。我的建议是除了应用账号外,双主节点上的MySQL只开root本地登录,复制账号严格控制网段,从连接源头上杜绝备库被直连写入的可能。

6.2 复制延迟突然变大

双主环境下,复制延迟一般都很低(毫秒级)。如果某天SHOW REPLICA STATUSSeconds_Behind_Source突然变成几十秒甚至几百秒,先别急着怀疑网络,多数情况是以下几种原因:

  1. 备库有大事务在跑(比如一次DELETE删了几百万行),binlog在备库重放时需要时间,这个属于正常现象,等它跑完就会追上。
  2. 备库的磁盘IO能力不足。复制线程在备库是单线程写盘,主库那边的并发写入在备库上会串行化。如果备库是机械盘而主库是SSD,延迟会非常明显。解决方法要么提高备库硬件,要么开启多线程复制。
  3. 大表DDL。MySQL 8.0的DDL虽然支持INSTANT算法,但一些场景(比如修改字段长度)仍需要重建表,在线DDL期间复制会停滞。

MySQL 8.0默认开启多线程复制(MTS),可以通过下面SQL看一下当前的配置:

SHOW VARIABLES LIKE 'replica_parallel_workers';

默认值是4。如果备库CPU核数充足,可以适当调大,比如改成8,提升并行回放能力。

6.3 GTID清理导致复制无法启动

GTID模式有个隐藏问题:如果主库长时间没有清理binlog,但GTID_PURGED集合一直膨胀,备库重新搭建复制时可能报错。反过来,如果你在主库执行过RESET MASTER或者手动清理了binlog,导致主库最老的GTID都大于备库当前已有的GTID,备库会报The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires.

这个问题最常见的诱因是手动删过binlog:

# 不推荐在生产环境这样操作 rm /data/mysql/logs/binlog.0000xx

正确做法是用MySQL自己的清理机制:

-- 推荐:设置自动过期清理 SET GLOBAL binlog_expire_logs_seconds = 86400 * 5; -- 或手动但安全的方式:PURGE BINARY LOGS BEFORE PURGE BINARY LOGS BEFORE NOW() - INTERVAL 5 DAY;

MySQL 8.0里expire_logs_days已经被binlog_expire_logs_seconds取代,这是另一个容易踩坑的点。

如果真遇到了GTID被清理的报错,重建备库数据是唯一可靠方案:

mysqldump --single-transaction --set-gtid-purged=ON --all-databases > /tmp/full_backup.sql

然后重新导入、重新CHANGE REPLICATION SOURCE TO。没有捷径可走。

6.4 MySQL 8.0 认证插件兼容性

前面提过caching_sha2_password的问题,这里再补充一个更具体的场景。如果是MySQL 8.0和其他版本(比如5.7)混合作双主,或者客户端工具版本很老,很容易遇到:

ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.

如果你的业务方连的还是8.0之前的老版本驱动,要么升级驱动,要么建用户时指定mysql_native_password。从长期看,我建议升级驱动,因为mysql_native_password在MySQL 8.0里虽然还能用,官方已在9.0版本中移除,到时候又得折腾一遍。

6.5 主从数据不一致的定期校验

双主架构里最怕的就是两个节点数据悄悄出现偏差,但这种偏差平时不一定会立刻暴露——直到某次切换时,业务数据对不上才发现,损失就大了。

我固定每周末凌晨跑一次数据校验:

-- Node A上对某张表做checksum CHECKSUM TABLE test_repl.t1;

然后去Node B执行同样的命令对比。数据量大的表,用CHECKSUM TABLE会比较慢,可以在业务低峰期跑。更精细的方案是用pt-table-checksum(Percona Toolkit),它可以分批对比并生成差异报告,是生产环境数据一致性校验的标配工具。双主架构建议把校验结果接入监控告警,一旦发现两个节点数据不一致,第一时间告警出来。

7. 最后一些经验之谈

这套双主热备方案上线至今跑了大概8个月,中间真实故障切换过2次,分别是主机硬件故障和一次误操作杀进程,整个切换过程业务侧基本没有感知,备库在秒级内接管了VIP。复盘来看,这套方案最大的价值其实不是“技术上的高级”,而是“故障发生时心里有底”。

如果要给还没上车的同学三条建议:

第一,双主热备不是拷贝一份配置、执行几条SQL就完事的。真正的功夫在故障演练和日常巡检上。建议每个月至少做一次主备切换演练,把kill进程、模拟断网、模拟磁盘满这些场景都过一遍,这样真出事的时候才不会手忙脚乱。

第二,监控必须到位。我这边把VIP状态、复制状态(Seconds_Behind_SourceReplica_IO_RunningReplica_SQL_Running)、binlog大小、磁盘空间都做了监控告警,任何一项异常都会在5分钟内通知到人。高可用不是为了不故障,而是为了故障时能快速发现、快速处置。

第三,回切流程要写成文档。从实际经验看,MySQL本身出问题的概率很低,反而是“切换过去了但忘了怎么回切”这种运维操作层面的问题更常见。把回切步骤、验证方式、失败回滚方案都白纸黑字写清楚,放到团队Wiki里,并让每个值班的同学都实际动手演练过,这才是这套配置能够长期稳定运行的最大保障。

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

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

立即咨询