做运维这几年MySQL的高可用方案我前后试过不少主从加手工切换、MHA、Orchestrator再到K8s里的Operator各有各的适用场景。但要论“最快让业务拥有一个虚IP、并且故障时能自动漂移”的性价比方案Keepalived绝对是绕不开的一个。标题里说5分钟咱不能真掐表但把配置核心思路理顺之后Keepalived相关的配置半小时内搞定是真能做到的。这篇文章就从方案选型、环境准备、配置细节到故障实测完整走一遍照着做就能跑起来。1. 整体方案设计与选型思路1.1 这个方案到底解决了什么问题先捋一下业务痛点。通常一个MySQL实例挂在某台服务器上客户端连接用的是这台机器的内网IP。一旦这台机器出现宕机、网络异常、MySQL进程崩溃客户端就连不上了只能等人工介入处理。哪怕你做了主从复制备库数据是完整的也得手动把连接切到备库去这个过程少则几分钟多则半小时业务影响不小。Keepalived这套方案的核心就是引入一个“虚IP”VIPVirtual IP客户端不再直连物理IP而是连VIP。Keepalived在主备两台机器上各自运行通过VRRP协议协商谁持有VIP。正常情况下Master持有VIP并提供服务Backup默默待命一旦Master故障、MySQL异常VIP会自动“漂移”到Backup上客户端重连或者连接池自动重连就能继续访问几乎感知不到后端发生了什么。这套方案适合什么场景呢我个人的判断是中小型团队、预算有限的内部系统、对数据一致性要求不算苛刻可以接受主从延迟的读写业务、以及DBA人力并不充裕的团队。它不是万能的和MHA、Orchestrator这类专职数据库高可用组件相比Keepalived本身并不感知MySQL数据同步状态它只做“IP漂移”这一件事。但它简单、直接、可控尤其对于“尽快恢复服务”这个目标来说非常够用。1.2 为什么在众多方案里选Keepalived说实话MySQL高可用方案现在选择非常多。MHA是Perl写的经典方案能做故障检测、日志补偿、自动提升主库但代码多年不怎么更新对新版本MySQL的兼容性、半同步复制的配合都稍显吃力。Orchestrator基于Raft做元数据管理功能强大但部署和运维心智成本偏高还需要额外的后端存储。数据库中间件ProxySQL、MyCat、ShardingSphere也能做读写分离和故障转移但把负载均衡和实际存储层的高可用混在一起排障链路会变长。Keepalived的优势恰恰是它足够“笨”。它只负责一件事用VRRP协议选出一个Master让VIP始终绑定在Master上Master挂了就换一个。这种设计看起来简单但生产环境里往往越简单的方案越稳定。加上它不侵入MySQL本身不需要改数据库配置不需要引入额外的代理节点资源占用极低一个VRRP实例只跑几个进程内存占用几MB多台机器之间只用一条UDP组播或单播报文互相确认存活网络开销可以忽略。所以我的选型结论是如果你的MySQL已经做好了主从或者双主只是缺一个自动切换入口那直接用Keepalived是最省事的。它不解决“MySQL主从数据落后”的问题那是复制策略的事它解决的是“故障发生后能不能快速恢复对外服务”的问题。把这两个问题分开思考方案边界就非常清晰了。2. 环境准备与基础搭建2.1 服务器规划和角色划分动手之前先把环境定清楚。以下是我在测试和生产中都验证过的标准形态节点角色说明IP地址说明db01MySQL主节点/Keepalived MASTER10.0.0.11初始持有VIP提供读写服务db02MySQL从节点/Keepalived BACKUP10.0.0.12故障时接管VIP提升为主节点虚拟IP对外服务入口10.0.0.100绑定在Master上故障时漂移操作系统选用CentOS 7.9或者Rocky Linux 8/9都行测试环境可以是虚拟机生产建议物理机或云主机均无问题。MySQL版本我这里用的是8.0.36其实5.7和8.0在Keepalived配合上没有本质区别。这里要说一个规划上的重要细节MySQL的主从复制关系和Keepalived的主备角色在初始部署时建议保持一致也就是db01既是复制主库也是Keepalived的MASTERdb02既是从库也是BACKUP。但Keepalived的MASTER和BACKUP只是一个“竞选初始优先级”的概念它不影响MySQL复制拓扑本身。后面如果发生了VIP漂移db02变成了对外提供服务的节点那么理想情况下应该把db01恢复后的复制关系反向调优为“新主旧备”让db01变为从库避免两个库同时写。为了简化运维我在生产环境其实更推荐双主模式互为主从这样VIP无论漂移到哪一台该节点都是可写状态不会出现从库只读导致业务写失败的情况。配合auto_increment_offset和auto_increment_increment的设置避免自增主键冲突双主写成两行配置就搞定了。当然双主也有双写的隐患比如同时在两边修改同一条数据会引发复制冲突需要在应用侧做好路由约束。我这里演示用的是双主结构简单直接你们可以根据自己业务情况选择。2.2 MySQL安装与双主复制配置MySQL的安装不是本文重点但双主复制我会简短讲一下关键参数。如果机器上还没装MySQL建议直接用官方Yum仓库装# 安装MySQL官方Yum仓库 yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm # 安装MySQL社区版 yum install -y mysql-community-server # 启动并设置开机自启 systemctl enable mysqld --now安装完成后编辑/etc/my.cnf在[mysqld]段下配置双主所需的核心参数。db01和db02的配置大体相同唯一不同的是server-id[mysqld] server-id1 log-binmysql-bin binlog_formatROW gtid_modeON enforce_gtid_consistencyON log_slave_updatesON skip_name_resolveON max_connections500 # 双主防自增冲突 auto_increment_offset1 auto_increment_increment2 # 默认开启只读避免双写冲突双主场景按需关闭 read_onlyOFFdb02把server-id改成2auto_increment_offset改成2即可。这里稍微解释一下这几个参数的意义log_slave_updatesON是为了让从库把“从主库同步来的变更”也写进自己的binlog这样互为从库时才能继续转发变更gtid_modeON是启用GTID复制简化主从切换后的链路重建auto_increment_offset/increment是为了两台机器自增ID不撞车。这几个参数在生产MySQL架构里几乎是标配建议直接抄。然后分别在两台机器上创建复制账号并构建复制链路。先在db01上执行-- 创建复制账号 CREATE USER repl10.0.0.% IDENTIFIED BY YourStrongPassword; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl10.0.0.%; FLUSH PRIVILEGES;db02上同样创建账号db01连接db02也要用然后两边互指-- 在db01上执行指向db02 CHANGE MASTER TO MASTER_HOST10.0.0.12, MASTER_USERrepl, MASTER_PASSWORDYourStrongPassword, MASTER_AUTO_POSITION1; START SLAVE; -- 在db02上执行指向db01 CHANGE MASTER TO MASTER_HOST10.0.0.11, MASTER_USERrepl, MASTER_PASSWORDYourStrongPassword, MASTER_AUTO_POSITION1; START SLAVE;查看复制状态确认Slave_IO_Running: Yes和Slave_SQL_Running: Yes双主复制就通了。这里要注意如果两台机器是全新初始化的MySQL直接GTID互指没问题如果是已经有业务的库需要先备份再恢复确保GTID集合一致后再搭复制不然报错会很烦躁。2.3 Keepalived安装前的系统准备Keepalived的安装很简单各发行版都有现成的包# CentOS/Rocky yum install -y keepalived # Ubuntu/Debian apt install -y keepalived但安装之前有几项系统层面的准备工作不能跳过。首先是防火墙VRRP协议默认走IP协议号112组播地址224.0.0.18如果机器上开着firewalld或iptables一定要放行。CentOS 7/8下面这样操作# firewalld放行VRRP firewall-cmd --permanent --add-rich-rulerule protocol value112 accept firewall-cmd --reload如果你不习惯用组播Keepalived也支持单播模式后面配置里会讲防火墙只需要放行主机之间的单播通信端口即可。我建议机房内网环境直接用单播更安全也更可控。其次是关闭可能干扰VIP绑定的NetworkManager对网卡的接管以及保证rp_filter反向路径过滤不会误伤VIP的入流量。生产上如果发现VIP能漂过去但外部访问不通多半和rp_filter有关。在/etc/sysctl.conf里确认net.ipv4.conf.all.rp_filter0 net.ipv4.conf.default.rp_filter0执行sysctl -p使其生效。这一步很容易被忽略但它恰恰是很多“VIP漂移后连不上”案例的根因。3. Keepalived配置逐行拆解3.1 vrrp_instance与全局配置的要点Keepalived的配置文件默认在/etc/keepalived/keepalived.conf。初次打开这个文件你会发现它主要由三块组成global_defs全局定义、vrrp_instanceVRRP实例、vrrp_script健康检查脚本。MySQL高可用场景下真正核心的是vrrp_script和vrrp_instance的配合。这里先给一份我实测可用的Master配置然后逐段解释global_defs { router_id LVS_DEVEL vrrp_skip_check_adv_addr vrrp_strict vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_script check_mysql { script /etc/keepalived/check_mysql.sh interval 2 fall 2 rise 2 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 nopreempt unicast_src_ip 10.0.0.11 unicast_peer { 10.0.0.12 } authentication { auth_type PASS auth_pass 123456 } track_script { check_mysql } virtual_ipaddress { 10.0.0.100/24 dev eth0 label eth0:1 } notify_master /etc/keepalived/notify.sh MASTER notify_backup /etc/keepalived/notify.sh BACKUP notify_fault /etc/keepalived/notify.sh FAULT }把配置文件读完你会发现Keepalived的工作逻辑非常直白。global_defs里最常见的坑是vrrp_strict这个选项。它开启之后Keepalived会强制使用VRRP协议规则比如不允许VIP地址和本机IP在同一网段之外绑定甚至会拦截非VRRP的报文。如果只是想快速做IP漂移我一般建议先注释掉vrrp_strict等生产环境网络策略都捋清楚之后再加上否则很容易出现“VIP明明在但就是不通”的诡异问题。vrrp_script是MySQL健康检查的入口它定义了一个可调用的脚本并指定了执行周期和判定规则。interval 2表示每2秒执行一次脚本fall 2表示连续失败2次才判定节点故障rise 2表示连续成功2次才判定节点恢复。这几个参数直接决定了故障转移的灵敏度。测试环境我觉得可以激进一点interval 1、fall 2总耗时2秒左右生产环境我建议interval 2、fall 3总耗时6秒稍微保守一些以免网络抖动引发不必要的切换。3.2 健康检查脚本的编写别只检查进程健康检查脚本是整个方案里最容易被写坏的地方。很多人图省事脚本里只写一句pgrep mysqld只要MySQL进程在就认为数据库是健康的。问题是进程在不代表数据库能正常接受连接。我曾经遇到过MySQL进入假死状态进程在但端口不响应、连接卡死的情况这时候如果不做“真实验证”Keepalived会认为一切正常VIP自然不会漂移业务照样不可用。我这边维护的检查脚本至少会做三件事检查进程、检查端口、实际执行一条SQL验证可读写。#!/bin/bash # /etc/keepalived/check_mysql.sh MYSQL_HOST127.0.0.1 MYSQL_PORT3306 MYSQL_USERkeepalived MYSQL_PASSWORDYourCheckPassword # 1. 检查进程是否存在 if ! pgrep -x mysqld /dev/null 21; then echo MySQL process not found exit 1 fi # 2. 检查端口是否LISTEN if ! ss -lnt | grep -q 0.0.0.0:${MYSQL_PORT}; then echo MySQL port ${MYSQL_PORT} not listening exit 1 fi # 3. 实际执行SQL验证可读写 if ! mysql -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PASSWORD} \ -e SELECT 1 /dev/null 21; then echo MySQL query failed exit 1 fi exit 0注意两点一是脚本执行账号要能被Keepalived调用且具备执行权限建议chmod x /etc/keepalived/check_mysql.sh二是MySQL里需要创建对应的检测账号并且只给只读权限不要用root当检测账号。我习惯创建一个专用账号CREATE USER keepalived127.0.0.1 IDENTIFIED BY YourCheckPassword; GRANT SELECT ON *.* TO keepalived127.0.0.1; FLUSH PRIVILEGES;有同学可能会问为什么要用127.0.0.1而不直接用socket连接因为socket连接只能证明本地进程存在用TCP连接可以顺便验证监听地址和端口是否对外正常更贴近真实故障场景。还有一点脚本返回非0时Keepalived会认为当前节点不健康从而降低优先级并释放VIP所以在脚本末尾务必用明确的exit 0/exit 1来表达结果。3.3 MASTER和BACKUP配置的差异与关键行对比Master配置Backup的配置只需要改动几个地方配置项MASTER节点BACKUP节点stateMASTERBACKUPpriority10090unicast_src_ip10.0.0.1110.0.0.12unicast_peer10.0.0.1210.0.0.11interfaceeth0eth0virtual_router_id5151virtual_router_id必须两边一致这是VRRP分组识别的依据两台机器之间就是通过它来相互感知的。priority相差10即可不能两边设成一样的值否则会出现抢VIP的抖动。关于nopreempt这里有个很容易踩的坑。默认情况下如果Backup优先级低在Master故障时接管了VIP等原Master恢复并重新加入VRRP组后它会因为优先级更高而强行抢回VIP这叫做“抢占模式”。对外表现就是VIP在两个节点间来回跳一次连接会瞬间中断。对于数据库这种长连接应用这种回切抖动很烦。所以我建议在Master配置中加上nopreempt让整个集群变成“非抢占模式”。设置了nopreempt之后原Master恢复时不会主动抢回VIPVIP会一直停留在接管节点直到下次故障或者人工介入。这在大多数场景下是合理的因为业务的连续优先于“让主库回到原来那台机器”。不过要注意nopreempt只在state MASTER的节点上设置才生效两边都要写。如果你确实需要原Master恢复后自动回切就删掉这个选项并把两边节点的state都配置成BACKUP这样只通过priority比较但切换时的抖动要做好心理准备。3.4 虚IP漂移的原理和优先级计算逻辑虚IP漂移这件事很多人用得很熟但真要问“它为什么能漂”能讲清楚的人不多。其实核心就是VRRP协议Virtual Router Redundancy Protocol虚拟路由冗余协议。Keepalived启动后会向组播地址224.0.0.18发送VRRP报文报文中包含当前节点的优先级和路由器ID。优先级高的节点会周期性通告自己“我还活着”优先级低的节点收到通告后知道自己不是Master就默默等待。MySQL故障时track_script检测到脚本退出码非0Keepalived会把该节点的优先级调低或者直接进入FAULT状态同时停止发送VRRP通告。Backup在连续几个advert_int周期内收不到Master的通告就认为Master挂了然后抢占成为新的Master在本机绑定VIP并向外发送免费ARPGratuitous ARP告诉交换机“这个IP的MAC地址变了”流量自然就引到新机器上。这一步是“先释放VIP再绑定VIP”的过程中间会有极短暂的时间窗口内VIP不在任何节点上。所以客户端如果用的是连接池要设置合适的连接超时时间保证断开的连接能快速重建而不是一直卡在旧连接上重试。通常这个窗口在1~3秒之间配置合理的话对业务的影响很小。4. 实操验证让VIP真正漂移一次4.1 初始状态确认与验证方法配置都写完两台机器上启动Keepalived第一件事是确认初始状态# 查看Keepalived日志 tail -f /var/log/messages # 查看VIP绑定状态 ip addr show eth0正常情况下db01MASTER上能看到eth0:1绑定着 10.0.0.100db02BACKUP上没有VIP地址。再用ip neigh或者抓包工具确认VRRP报文在正常收发# 在db01上开启抓包观察VRRP报文 tcpdump -i eth0 -n vrrp如果一直有vrrp: Coming up之类的输出说明协议通信正常。这时候从任意一台业务机器上ping 10.0.0.100应该能通并且响应来自db01。切换到db02上做同样的检查BACKUP节点的日志里应该能看到enter BACKUP state等字样但不会绑定VIP。这一步确认OK基础的初始状态就对了。4.2 模拟Master故障观察VIP切换为了验证漂移效果最直接的办法是让db01的mysqld进程直接“假死”# 在db01上执行 systemctl stop mysqld然后观察两个节点和VIP的变化。清空db01和db02上的Keepalived日志方便记录切换时间点。16:00:00执行systemctl stop mysqld后db01上的check_mysql.sh会在2秒左右检测到MySQL不可用继续失败1次后fall 2Keepalived在16:00:05左右把db01置为FAULT状态VIP从db01上解绑。db02在1秒内没收到db01的VRRP通告加上自己的检查脚本确认本机MySQL正常随即提升为MASTER绑上10.0.0.100并在网卡上发出免费ARP广播。整个过程我在实际测试中耗时大概在4~6秒之间。用手机秒表能感知到大概“断了5秒网”对一般业务来说可接受但如果你的业务对中断极其敏感可以通过调小interval和fall来提速不过要承担误判的风险。4.3 Recovery恢复Master后的行为与配置经验Master恢复的测试同样重要。在db01上把MySQL拉起来systemctl start mysqld这时候MySQL从库会自动追上db02的binlog双主配置下Keepalived恢复心跳。由于我在配置里加了nopreemptdb01即使状态恢复优先级更高也不会抢回VIPVIP继续留在db02上。这在生产环境是我推荐的做法原因前面说过了避免VIP来回抖。但要注意这意味着长期来看db02是实际对外提供服务的主库db01变成它的从库业务访问入口并没有切回到原主库。如果需要回切操作步骤是先停掉db02上的Keepalived让VIP自动漂回db01再启动db02的Keepalived让它继续当BACKUP。这样回切是平滑的不会出现两边同时持有VIP的脑裂。我见过不少同学忽略了这个“非抢占”和“回切”的关系生产上出现“明明主库已经修好了VIP怎么还在备机上”的困惑其实这不是故障而是nopreempt的预期行为。所以配置前就想清楚你是要“自动回切”还是“保持当前可用节点不抖动”这两者在Keepalived里天生是互斥的。5. 常见问题与排查技巧实录5.1 常见问题排查速查表我把实操中遇到的问题整理成一张速查表覆盖了90%以上的故障场景照着顺序排查基本上都能定位现象可能原因排查步骤 / 解决办法两台机器都绑定了VIP防火墙拦截VRRP报文主备互相感知不到对方检查firewall-cmd --list-all放行protocol 112或改用unicast单播VIP漂移过去了但外部无法访问路由器的ARP表未刷新或源机器rp_filter拦截手动刷ARP检查 sysctl rp_filter0设备端网卡开启arp_ignore/arp_announceVIP在MASTER上但切换不触发健康检查脚本失败/根本没执行手动执行脚本看返回值检查脚本权限、MySQL账号权限journalctl -u keepalived看日志切换耗时过长interval/fall配置过大调整vrrp_script的 interval 和 fall或缩短 advert_int恢复原主后VIP不回切配置了nopreempt了解这是预期行为按需手动回切或去掉nopreempt并配置回切策略Keepalived报PASSWORD错误auth_pass 过期/不一致两边配置里auth_pass必须完全一致且不超过8位老版本限制5.2 通知脚本与监控平台联动VIP漂移发生了如果运维同学完全不知道那这“高可用”就打了折扣。Keepalived提供了notify_master、notify_backup、notify_fault三个钩子分别在节点状态变化时触发脚本很适合接告警通知。我写了一个简单的通知脚本状态切换时发一条消息到企业微信/钉钉机器人#!/bin/bash # /etc/keepalived/notify.sh TYPE$1 IP$(ip addr show eth0 | grep inet | awk {print $2}) case $TYPE in MASTER) MESSAGEMySQL高可用: $(hostname) 成为MASTERVIP绑定中。本机IP: ${IP} ;; BACKUP) MESSAGEMySQL高可用: $(hostname) 转为BACKUP待命中。本机IP: ${IP} ;; FAULT) MESSAGEMySQL高可用: $(hostname) 进入FAULT状态请立即检查本机IP: ${IP} ;; *) exit 0 ;; esac # 这里替换成你的webhook地址 curl -s -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyYOUR_KEY \ -H Content-Type: application/json \ -d {\msgtype\:\text\,\text\:{\content\:\${MESSAGE}\}}脚本记得加执行权限并在Keepalived配置文件中通过notify_master、notify_backup、notify_fault引用它。这样一旦发生过切换运维能第一时间收到告警随后去检查原主库的故障原因而不是被业务侧“数据库怎么卡了一下”的反馈被动搅得焦头烂额。5.3 脑裂问题的现实处理与防范提到Keepalived和MySQL高可用必然绕不开“脑裂”这个词。所谓脑裂就是主备两台节点都认为自己是唯一活跃的Master都试图提供服务、都持有VIP造成业务写入分裂到两个MySQL实例上双主场景下数据一致性会被破坏。现实情况是MySQL高可用的“脑裂”不完全等同于网络分区常见诱因有两个一是VRRP通信中断网络抖动/防火墙拦截二是健康检查脚本在两边都判定失败。第一个情况就是两台机器网络不通但各自MySQL都正常于是各自抢占VIP第二个情况比较隐蔽比如脚本里用的MySQL密码过期了两边都检查失败两个节点都进入FAULT状态VIP反而没人持有。我的应对方法是三层防护。第一VRRP层面优先使用unicast_peer单播通信明确指定对端IP避免组播在复杂网络中的不确定性。第二Keepalived检测与MySQL健康状态强绑定并且加上冗余检查项不只依赖一个脚本。第三应用层连接必须走VIP但连接池要配置“读写分离”路由策略确保即使出现了极端脑裂写入也只打到持有VIP的节点降低数据损坏概率。当然最稳妥的兜底是数据文件与binlog的定期备份。Keepalived的漂移只是减少RTO绝不能替代备份体系。这一点无论方案多完善都不能省。6. 这个方案的天花板与扩展路径再回过头来说说影响范围。KeepalivedMySQL这套方案能覆盖很大一部分场景但也有明显边界。它不感知MySQL复制延迟切换时如果主从延迟过大会丢最近的写入它对双写冲突没有防护能力它无法像专门的HA组件那样提供“半同步复制”的联动保障。那什么场景下需要升级如果业务对数据一致性要求更高比如金融交易类系统建议考虑MySQL半同步复制配合MHA或者Orchestrator如果团队已经有K8s环境直接用Operator管理MySQL实例把故障转移交给控制器会更省心如果只是读多写少的内部工具这套Keepalived方案足够用很久。我个人经历里用Keepalived支撑过多个日均几十万请求的内部系统最长稳定运行超过两年期间发生过几次物理机维护和一次硬件故障VIP漂移都能达到预期效果。这套办法的适用性和稳定性是经过时间验证的。最后给一个建议如果你刚准备在生产环境上这个方案先用模拟脚本频繁杀掉MySQL进程多测几轮确认漂移时间、回切行为、告警通知都符合预期再切业务。做高可用纸上谈兵没有用必须真正打几场“演习”后面真出故障时才不会慌。