Keepalived 不能直接实现 MySQL 主从高可用,因其仅负责 VIP 漂移且不感知复制状态;必须先构建双主架构并配置自检脚本、合理参数及应用层连接池校验才能真正高可用。

一个常见的误解是:给 MySQL 主从架构配上 Keepalived,就万事大吉了。事实恰恰相反,Keepalived 只管 VIP 漂移,它根本不关心数据库的复制状态。真想实现高可用,必须先搭建双主(master-master)架构,再用 Keepalived 管理 VIP。否则,主库一旦宕机,从库不会自动切换为主,VIP 漂过去也是白搭,业务照样中断。
为什么不能直接在主从上配 Keepalived
Keepalived 的核心机制 vrrp_instance 只负责检测本地服务是否存活或端口是否通畅,它对 MySQL 复杂的复制状态一无所知。想象一下这个场景:VIP 绑定在主库上,从库也配置了 Keepalived 但作为 BACKUP 角色。一旦主库宕机,VIP 确实会漂移到从库。但问题来了,此时从库的复制线程(Sla ve_IO_Running 和 Sla ve_SQL_Running)很可能早已停止,或者存在几分钟甚至更久的复制延迟。如果应用直接连上这个“新主”,结果就是查不到最新数据,甚至写入会报错,数据一致性完全无法保证。
这种配置下,通常会遇到以下几种“诡异”现象:
- Keepalived 切换后,应用能通过 VIP 连上数据库,但就是读不到刚刚写入的数据。
- 切换完成后,执行
SHOW SLA VE STATUS一看,Seconds_Behind_Master这个延迟值高达几百甚至几千秒。 - 误以为“VIP 能连通就等于数据库正常可用”,结果业务接连报出主键冲突或唯一索引重复的错误。
双主 + Keepalived 的核心配置要点
搭建双主架构,可不是简单地在两台机器上互相执行 CHANGE MASTER TO 命令就完事了。关键在于配置参数必须错开,否则两边同时写入,数据冲突将不可避免:
server-id必须不同:例如,一台设为1,另一台设为2。auto_increment_offset和auto_increment_increment必须配对设置:比如,两台都设置auto_increment_increment = 2,然后一台的auto_increment_offset设为1,另一台设为2。这样能确保自增ID不会冲突。- 两台服务器都必须开启二进制日志(
log-bin),并且binlog_format建议使用ROW模式。行级复制在双主架构下比语句级复制更可靠,能避免许多潜在问题。 - 务必确认关闭了
skip_sla ve_start参数(MySQL 5.7 及以上版本默认已关闭),以确保数据库重启后,复制线程能自动恢复工作。
以下是一个配置示例片段(位于 /etc/my.cnf):
[mysqld] server-id = 1 log-bin = mysql-bin binlog-format = ROW auto_increment_offset = 1 auto_increment_increment = 2
Keepalived 配置里最容易漏掉的检测逻辑
Keepalived 默认的 TCP_CHECK 健康检查,仅仅检测 3306 端口是否能连通。这意味着,即使 MySQL 进程还在,但复制线程已经卡死或停止,Keepalived 依然会认为该节点是“健康”的。这显然不行。因此,必须添加自定义的检查脚本,来判断 MySQL 的复制状态是否真正可用:
- 脚本需要检查
Sla ve_IO_Running和Sla ve_SQL_Running是否都为Yes。 - 最好再加上对复制延迟的判断,例如
Seconds_Behind_Master <= 5(具体阈值可根据业务对数据实时性的容忍度调整)。 - 在 Keepalived 配置中,通过
vrrp_script块来定义并调用这个脚本,作为判定节点健康状态的依据。同时,利用notify_down等机制来触发故障处理。 - 强烈建议将
nopreempt参数设为yes,这可以避免原主库恢复后,重新抢回 VIP 而导致“脑裂”现象。
一个典型的错误配置是:只在 virtual_server 部分配置了 TCP_CHECK,而没有配置 vrrp_script,或者自定义脚本的返回值没有被 Keepalived 正确识别。
真实故障场景下最常被忽略的一点
即便双主架构和 Keepalived 都配置得天衣无缝,检测脚本也运行无误,还有一个环节常常在故障发生时被忽略:应用层的连接池。当主写节点宕机,VIP 成功漂移到备用节点后,应用服务器连接池里那些旧的、指向原主库的数据库连接并不会自动断开和重连。如果应用没有配置连接有效性校验(比如在每次从连接池借用连接前,执行一次 PING 或简单的 SELECT 1),那么应用就会继续向已经失效的旧主库发送请求。这些请求要么超时,要么被丢弃,直到连接池因超时回收这些旧连接。在这段“空窗期”内的所有写操作,都将丢失。
这个问题,既不是 Keepalived 的锅,也不是 MySQL 的错,而是应用层必须配合完成的“最后一公里”。解决方案是:确保数据库连接池启用了类似 testOnBorrow 的机制,并且用于测试的 SQL 语句最好能暴露复制中断的问题(例如,查询一个近期有写入的表,或者结合 SELECT @@read_only 来辅助判断节点角色)。否则,整个高可用链路就会在这最后一环功亏一篑。
