mysql双主热备必须搭配keepalived才能实现真正高可用,因其本身仅提供双向复制,无法自动故障切换、防脑裂或解决写冲突;keepalived通过vip漂移和mysql状态检测脚本(检查slave_io/sql_running)实现秒级无感切换,且需严格配置server-id、auto-increment-offset、log-slave-updates及初始数据一致性。

MySQL 双主热备(Master-Master)不是“开箱即用”的高可用方案,它本身不自动切换、不防脑裂、不解决写冲突,**必须搭配 Keepalived 或类似 VIP 管理工具才能对外提供稳定服务**。单独配双主,只是实现了双向复制,离“可用”还差关键一步。
为什么双主必须配 Keepalived 才算真正热备
双主本质是两台 MySQL 互为 Master 和 Slave,但应用层没有统一入口:客户端直连哪台?故障时怎么切?手动改 IP?这根本不算热备。
Keepalived 的作用就是提供一个浮动的虚拟 IP(VIP),始终绑定在健康节点上,并通过 check_mysql.sh 类脚本持续探测数据库状态。一旦某台宕机,VIP 秒级漂移到另一台,业务无感知。
常见错误现象:
– 只配了双主,没配 Keepalived,结果主库挂了,应用连不上,以为“热备失效”;
– Keepalived 检测脚本只 ping 3306 端口,但 MySQL 进程卡死、复制中断、Slave_IO_Running 为 No 却没被发现,VIP 错误地留在故障节点上。
- 检测脚本必须检查:
mysql -e "show slave status\G" | grep -E "(Slave_IO_Running|Slave_SQL_Running): Yes" - VIP 绑定网卡要和实际业务网卡一致(比如
eth0,不是lo) - 两台服务器的
priority值不能相同,否则可能同时抢 VIP 导致脑裂
server-id、auto-increment-offset 和 log-slave-updates 必须严格配对
这是避免自增主键冲突和循环复制的核心。双主不是简单把两台主从反过来再配一遍,参数必须错开且互补。
假设两台机器 IP 分别为 192.168.1.100 和 192.168.1.101:
-
192.168.1.100的my.cnf中设:server-id = 100,auto-increment-increment = 2,auto-increment-offset = 1 -
192.168.1.101的my.cnf中设:server-id = 101,auto-increment-increment = 2,auto-increment-offset = 2 - 两台都必须开启:
log-bin、log-slave-updates = ON、binlog_format = MIXED或ROW
漏掉 log-slave-updates 是最常踩的坑:它让从库把自己收到的变更再写进自己的 binlog,否则另一台无法继续复制,形成断链。
初始数据一致性比配置更重要
双主启动前,两台数据库内容必须完全一致。任何差异都会导致后续复制报错(如 Duplicate entry、Table doesn't exist),甚至静默丢数据。
推荐做法(停机窗口允许时):
- 在主 A 上执行:
FLUSH TABLES WITH READ LOCK;+SHOW MASTER STATUS;记下File和Position mysqldump -uroot -p --all-databases --master-data=2 > full.sqlUNLOCK TABLES;- 把
full.sql导入主 B,导入后不要重启MySQL,直接在 B 上执行:CHANGE MASTER TO MASTER_LOG_FILE='xxx', MASTER_LOG_POS=yyy;(用 A 的 File/Position) - 然后启动
START SLAVE;,等同步追平再反向配另一条链路
如果无法停机,务必用 pt-table-checksum 校验并修复差异,别信“刚初始化应该一样”这种假设。
复制延迟和冲突的实际影响远超预期
双主不是“写任意一台都行”。当应用并发写两台时,UPDATE 同一行、DELETE 同一条记录、甚至 INSERT 自增 ID 冲突(哪怕 offset 配对,也可能因事务顺序引发唯一键冲突),都会导致复制中断。
典型表现:Seconds_Behind_Master: NULL,Slave_SQL_Running: No,错误日志里出现 Could not execute Write_rows event on table xxx; Duplicate entry '1' for key 'PRIMARY'。
应对策略只有两个:
- 应用层强制所有写请求走 VIP(即只写当前主节点),读可分发到两台 —— 这才是合理用法
- 若真需双写,必须引入分布式锁或业务层序列化,
MySQL本身不解决这个问题
别指望 slave-skip-errors 能兜底:跳过错误等于丢数据,且跳过一次后可能引发连锁错位,最终全表不同步。
chronyd 必须指向同一 NTP 源,否则 SHOW MASTER STATUS 的时间戳偏差会影响故障定位;iptables 或 firewalld 必须放行 3306(复制)、53(Keepalived 组播)、以及脚本探测用的本地端口(如 6000)。这些细节不处理,VIP 漂移会失败,复制会卡住。











