MySQL高可用进阶:半同步复制+Keepalived+逻辑判断实现智能主从切换

发布时间:2026/8/14 19:38:05
MySQL高可用进阶:半同步复制+Keepalived+逻辑判断实现智能主从切换 1. 项目缘起为什么我们需要一个“带脑子”的自动切换方案在数据库运维的日常里MySQL高可用是个老生常谈的话题。一提到主从切换很多人的第一反应就是Keepalived VIP漂移这套组合拳确实经典部署简单响应也快。但真正在生产环境里跑过几年的人都知道这套方案的“傻”也是出了名的——它只管VIP漂不漂可不管数据库节点本身是不是真的“健康”更不管数据是不是真的同步到位了。我就亲身经历过一次“惊心动魄”的切换。当时主库因为一个长事务导致IO线程短暂卡顿从库的复制延迟瞬间飙升到几十秒。偏偏这个时候主库的网络又抖了一下Keepalived的检测脚本只是简单地mysql -h 127.0.0.1 -e ‘select 1’返回成功就判定主库健康。结果VIP“唰”一下就漂到那个延迟几十秒的从库上去了。应用连上去一读写全是旧数据业务逻辑全乱套差点酿成一次数据错乱的生产事故。那次之后我就明白高可用不能只靠“心跳”还得靠“脑子”。我们需要一个能判断数据一致性、复制状态等逻辑状态的切换方案这就是今天要聊的“半同步复制Keepalived第三方数据逻辑判断”这套组合拳的由来。它不是一个新发明的轮子而是对经典方案一次至关重要的“智力升级”。2. 方案核心架构让VIP漂移学会“看数据”这套方案的目标很明确在发生故障时自动将VIP切换到数据最完整、最可靠的从库上而不是简单地切换到第一个活着的从库。为了实现这个目标我们需要三个核心组件各司其职协同工作。2.1 组件一MySQL半同步复制——数据一致性的基石首先我们得确保在主库写入时数据能“安全地”到达至少一个从库。异步复制是“发了就不管”全同步复制是“等所有人都确认”太慢半同步复制Semisynchronous Replication则是个折中的聪明选择。它的工作原理是主库执行完一个事务后并不立即返回给客户端成功而是等待至少一个从库接收并写入到自己的中继日志Relay Log后才向客户端确认提交成功。如果超过设定的超时时间rpl_semi_sync_master_timeout默认10秒没有从库确认主库会自动降级为异步复制模式保证业务不挂死。为什么它是基石因为它从源头极大地降低了数据丢失的风险。在主库宕机的极端情况下我们几乎可以确定那个成功ACK了主库的从库拥有宕机前最后一刻的、已提交的事务数据。这为我们后续选择哪个从库作为新主库提供了最关键的数据完整性依据。没有这个机制我们所谓的“逻辑判断”就成了无源之水因为你无法确认哪个从库的数据最新、最全。2.2 组件二Keepalived——VIP漂移的执行者Keepalived在这里的角色很纯粹就是一个高可用的IP地址管理工具。它通过VRRP协议在多个服务器之间虚拟出一个VIPVirtual IP。所有应用都通过这个VIP来访问数据库。Keepalived本身通过vrrp_script定义健康检查脚本。传统做法里这个脚本只检查MySQL进程是否存在、端口是否可连接。但在我们的方案里这个脚本被赋予了更重要的使命——它不仅要检查“活着”还要调用一个外部的“大脑”第三方逻辑判断脚本来询问“我现在这个节点有资格持有VIP吗”Keepalived根据这个脚本的返回码0为成功非0为失败来决定是否放弃或抢占VIP。它不关心判断逻辑有多复杂只忠实地执行“大脑”的指令。这种解耦的设计非常清晰Keepalived干它最擅长的网络层故障转移把复杂的业务逻辑判断交给更专业的脚本去做。2.3 组件三第三方逻辑判断脚本——方案的大脑这是整个方案的灵魂也是区别于普通方案的核心。这个脚本通常是一个Shell或Python脚本它需要完成以下几项关键检查本机MySQL服务状态基础检查确保mysqld进程正常可以连接。复制状态检查Slave_IO_Running和Slave_SQL_Running是否都为YesSeconds_Behind_Master复制延迟是否在可接受的阈值内例如小于30秒是否有复制错误Last_IO_Errno,Last_SQL_Errno半同步复制状态检查关键查询Rpl_semi_sync_slave_status是否为ON确认本机半同步插件运行正常。更重要的是判断本机是否为“那个”确认了主库事务的从库。这可以通过检查一些内部状态或者结合时间戳和GTID位置来综合推断。一个简单的思路是在发生切换时延迟最小且半同步状态正常的从库极大概率就是最新的从库。只读状态检查确保read_onlyON对于从库。一个持有VIP的从库在提升为主库前必须保证自己是只读的避免双写。集群状态仲裁可选但推荐脚本不应该只检查自己。一个更健壮的“大脑”应该能通过查询其他节点或者查询一个共享的配置中心如ZooKeeper、etcd甚至一个简单的数据库表来了解整个集群的拓扑和状态从而做出更全局化的决策比如避免“脑裂”。这个脚本的返回值直接决定了Keepalived的行为。只有当一个节点通过了所有逻辑检查认为自己“健康且数据完整”时它才返回0告诉Keepalived“我有资格成为主库请把VIP给我。”3. 部署与配置实战从零搭建智能高可用集群理论说再多不如动手搭一遍。下面我们以一个典型的两节点主从架构为例手把手走通整个部署流程。假设我们有两台服务器node1 (192.168.1.101)计划作为初始主库node2 (192.168.1.102)作为初始从库VIP为192.168.1.100。3.1 MySQL半同步复制配置首先在两台服务器上安装相同版本的MySQL建议5.7或8.0并完成基础的异步主从复制搭建。这里假设你已经配置好了server_idlog_bin并创建了复制账号。在主库node1上-- 安装半同步主插件 INSTALL PLUGIN rpl_semi_sync_master SONAME semisync_master.so; -- 启用半同步 SET GLOBAL rpl_semi_sync_master_enabled ON; -- 设置超时时间毫秒根据网络情况调整 SET GLOBAL rpl_semi_sync_master_timeout 1000; -- 持久化到配置文件防止重启失效 -- 在my.cnf的[mysqld]段添加 -- plugin-loadrpl_semi_sync_mastersemisync_master.so -- rpl_semi_sync_master_enabled1 -- rpl_semi_sync_master_timeout1000在从库node2上-- 安装半同步从插件 INSTALL PLUGIN rpl_semi_sync_slave SONAME semisync_slave.so; -- 启用半同步 SET GLOBAL rpl_semi_sync_slave_enabled ON; -- 重启IO线程以使半同步生效 STOP SLAVE IO_THREAD; START SLAVE IO_THREAD; -- 持久化配置 -- plugin-loadrpl_semi_sync_slavesemisync_slave.so -- rpl_semi_sync_slave_enabled1配置完成后在主库执行SHOW STATUS LIKE ‘Rpl_semi_sync%’;关注Rpl_semi_sync_master_status应为ONRpl_semi_sync_master_yes_tx会随着事务提交而增加表示事务通过半同步方式确认。3.2 编写核心“大脑”逻辑判断脚本这是最关键的一步。我们在/usr/local/bin/目录下创建一个脚本mysql_ha_check.sh并赋予执行权限。#!/bin/bash # mysql_ha_check.sh # 返回值0-健康可持有VIP非0-不健康应放弃VIP MYSQL_HOST127.0.0.1 MYSQL_PORT3306 MYSQL_USERcheck_user # 创建一个仅有查询权限的监控账号 MYSQL_PASSYourStrongPassword MAX_DELAY30 # 最大允许延迟秒数 # 1. 基础连接检查 if ! mysqladmin -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PASS} ping /dev/null; then echo MySQL service is down. exit 1 fi # 2. 查询复制状态和关键指标 SQLSHOW SLAVE STATUS\G SLAVE_STATUS$(mysql -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PASS} -e $SQL 2/dev/null) if [ -z $SLAVE_STATUS ]; then # 可能这是主库或者SHOW SLAVE STATUS无输出 # 检查是否只读如果是从库转主库这里应该为OFF READ_ONLY$(mysql -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PASS} -sN -e SELECT global.read_only) if [ $READ_ONLY -eq 1 ]; then echo Node is in read-only mode but not a slave? Check topology. exit 2 fi # 如果是主库角色可以进一步检查半同步主状态、连接数等 # 此处简化处理认为可写的主库是健康的 echo Node is a writable master. exit 0 fi # 解析从库状态关键字段这里使用grep和awk简单处理生产环境建议用更健壮的方式 IO_RUNNING$(echo $SLAVE_STATUS | grep -i Slave_IO_Running: | awk {print $2}) SQL_RUNNING$(echo $SLAVE_STATUS | grep -i Slave_SQL_Running: | awk {print $2}) SECONDS_BEHIND$(echo $SLAVE_STATUS | grep -i Seconds_Behind_Master: | awk {print $2}) LAST_IO_ERROR$(echo $SLAVE_STATUS | grep -i Last_IO_Errno: | awk {print $2}) SEMI_SYNC_STATUS$(mysql -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PASS} -sN -e SHOW STATUS LIKE Rpl_semi_sync_slave_status; | awk {print $2}) # 3. 逻辑判断 # 判断1: 复制线程是否正常 if [ $IO_RUNNING ! Yes ] || [ $SQL_RUNNING ! Yes ]; then echo Slave threads are not running properly. IO: $IO_RUNNING, SQL: $SQL_RUNNING exit 3 fi # 判断2: 复制延迟是否过大 if [ $SECONDS_BEHIND NULL ] || [ $SECONDS_BEHIND -gt $MAX_DELAY ]; then echo Replication delay is too high: $SECONDS_BEHIND seconds. exit 4 fi # 判断3: 是否有复制错误 if [ $LAST_IO_ERROR -ne 0 ]; then echo There is a replication IO error. exit 5 fi # 判断4: 半同步复制是否正常 if [ $SEMI_SYNC_STATUS ! ON ]; then echo Semisynchronous replication is not active. exit 6 fi # 判断5: 必须处于只读模式 READ_ONLY$(mysql -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PASS} -sN -e SELECT global.read_only) if [ $READ_ONLY -ne 1 ]; then echo Slave is not in read-only mode. Potential risk for dual-write. exit 7 fi # 所有检查通过 echo Slave is healthy, replication is semi-sync and within delay limit. exit 0这个脚本实现了我们之前讨论的核心逻辑检查。它在从库上运行只有复制完全正常、延迟低、半同步开启且处于只读状态时才返回0。3.3 Keepalived配置与集成在两台节点上安装Keepalived。配置文件通常位于/etc/keepalived/keepalived.conf。在node1 (初始主库) 上的配置global_defs { router_id mysql_node1 # 唯一标识 } vrrp_script chk_mysql_ha { script /usr/local/bin/mysql_ha_check.sh # 调用我们的“大脑”脚本 interval 2 # 检查间隔秒数 weight -5 # 如果检查失败优先级降低5 fall 2 # 连续2次失败才认为失败 rise 1 # 1次成功就认为恢复 } vrrp_instance VI_1 { state BACKUP # 两台都设为BACKUP通过优先级竞争避免脑裂 interface eth0 # 网卡名称 virtual_router_id 51 # 虚拟路由ID集群内唯一 priority 100 # 初始优先级node1稍高 advert_int 1 authentication { auth_type PASS auth_pass 1111 # 密码集群内一致 } virtual_ipaddress { 192.168.1.100/24 dev eth0 # VIP } track_script { chk_mysql_ha # 跟踪健康检查脚本 } # 当本节点成为MASTER时执行的脚本用于提升为主库关闭只读确保写权限 notify_master /etc/keepalived/notify_master.sh # 当本节点成为BACKUP时执行的脚本用于降级为从库开启只读 notify_backup /etc/keepalived/notify_backup.sh }在node2 (初始从库) 上的配置与node1基本一致主要修改router_id为mysql_node2priority设置为一个低于100的值比如98。这样在双方都健康时VIP会落在优先级更高的node1上。notify_master.sh和notify_backup.sh脚本用于在VIP漂移时自动执行数据库角色切换。这是实现自动故障转移Failover的关键一步而不仅仅是VIP切换。notify_master.sh (当节点获得VIP需提升为主库时执行)#!/bin/bash # 停止复制重置主从关系 mysql -e STOP SLAVE; RESET SLAVE ALL; # 关闭只读模式 mysql -e SET GLOBAL read_onlyOFF; # 刷新权限表确保更改生效某些版本需要 mysql -e FLUSH PRIVILEGES; # 记录日志 logger Keepalived: This node is now the MASTER. VIP acquired, read_only disabled.notify_backup.sh (当节点失去VIP需降级为从库时执行)#!/bin/bash # 开启只读模式 mysql -e SET GLOBAL read_onlyON; # 注意这里不会自动重新配置主从。需要额外的逻辑或者依靠MHA、orchestrator等工具来重构复制拓扑。 # 记录日志 logger Keepalived: This node is now a BACKUP. VIP lost, read_only enabled.配置完成后启动两边的Keepalived服务systemctl start keepalived systemctl enable keepalived。使用ip addr show eth0命令查看VIP在哪台机器上。4. 故障模拟与切换逻辑全解析纸上谈兵终觉浅我们来模拟几种典型的故障场景看看这套“带脑子”的方案如何应对。4.1 场景一主库MySQL进程崩溃这是最直接的故障。node1上的mysqld进程意外终止。node1上的mysql_ha_check.sh脚本执行mysqladmin ping失败脚本返回非0。Keepalived检测到chk_mysql_ha脚本失败根据weight -5的配置将node1的优先级从100降至95。node2的优先级为98高于node1的95。node2在下一个VRRP通告周期中发现自己的优先级更高于是发起选举将VIP192.168.1.100抢占到自己身上。node2的Keepalived状态变为MASTER触发notify_master.sh脚本执行。脚本停止node2的复制线程并关闭read_only模式。此时node2正式成为可读写的新主库。所有应用程序通过VIP连接到新的主库node2写入继续。关键点切换的发生不仅仅因为node1的MySQL挂了更因为node2通过了所有逻辑检查复制正常、延迟低、半同步开启证明自己是一个合格的继任者。4.2 场景二主库网络中断脑裂风险防范node1所在服务器网络完全断开但MySQL进程正常。node1无法与node2通信但它自己检查自己是“健康”的脚本返回0。它认为自己是唯一存活的节点会继续持有VIP。但由于网络隔离应用实际上无法访问到这个VIP。node2也收不到node1的VRRP通告它会认为node1下线了。node2检查自身状态通过于是将自身优先级提升因为认为没有竞争对手并宣告自己为MASTER抢占VIP。此时网络分区导致出现了两个“主库”都认为VIP在自己这里这就是“脑裂”。我们的方案如何缓解第三方脚本的增强我们的mysql_ha_check.sh可以进一步增强加入对原主库可达性的判断。例如脚本可以尝试连接原主库的IP或者查询一个共享的仲裁节点如一个轻量级的consul服务。如果发现原主库还活着只是网络不通那么当前节点即使自身健康也主动放弃竞选脚本返回非0从而避免在分区时形成双主。这需要更复杂的脚本逻辑但能极大提升集群的健壮性。Keepalived的nopreempt与priority设计我们将两台机器初始状态都设为BACKUP并设置不同的priority这本身比一主一备MASTER/BACKUP的模式更能减少脑裂概率。结合严格的脚本检查能在大多数网络抖动场景下保持稳定。4.3 场景三从库复制延迟过大这是传统方案最容易出错的地方。假设node2从库因为一个大的批量操作复制延迟达到了2分钟。此时node1主库依然健康。node2上的mysql_ha_check.sh脚本执行。检查到Seconds_Behind_Master大于我们设定的MAX_DELAY30秒脚本返回退出码4非0。Keepalived检测到脚本失败降低node2的优先级。即使此时node1发生故障node2因为自身检查不通过优先级被降低它也不会去抢占VIP。VIP可能会漂移到其他健康的从库如果有多于两个节点或者停留在故障的主库上直到脚本超时或检测到主库故障。这阻止了一次危险的数据不一致切换。运维人员会收到报警监控应监控脚本的退出码和MySQL延迟然后人工介入处理延迟问题。这体现了“自动切换”与“人工兜底”的结合自动化处理明确、简单的故障将复杂、有风险的场景留给人工判断。5. 方案优化与生产环境进阶思考上面的基础方案已经能解决大部分问题但在严苛的生产环境中我们还需要考虑更多。5.1 判断脚本的增强与仲裁机制基础的本地检查脚本存在局限性它只有一个节点的视角。一个更健壮的“大脑”应该具备集群视角。探针式检查脚本可以内置一个小型探针主动去连接集群中的其他节点询问“你认为谁是主库”、“我的数据是不是最新的”。这需要节点间有一个简单的HTTP接口或共享的数据库表来交换信息。引入外部仲裁器这是更常见的做法。可以使用ZooKeeper、etcd或Consul作为分布式协调服务。每个MySQL节点定期向仲裁器注册自己的状态GTID、角色、健康状态。判断脚本不再只检查自己而是去仲裁器查询“根据集群全局状态我现在应该是主库吗”仲裁器可以根据预设策略如GTID最大者胜出给出权威答案彻底解决脑裂问题。许多成熟的高可用工具如Orchestrator其核心就是一个强大的拓扑管理和仲裁器。5.2 多从库环境下的优先级设计当集群有多个从库比如一主三从时VIP应该漂移到哪个从库这就需要更精细的优先级设计。基于半同步ACK的优先级哪个从库的Rpl_semi_sync_slave_status是ON且最近有ACK哪个优先级就最高。这需要脚本能查询或推断出ACK信息。基于GTID/位点的优先级脚本比较各从库包括自己的Executed_Gtid_Set拥有最靠后GTID的从库优先级最高。这需要脚本能访问其他节点的信息通过仲裁器或直接查询。基于延迟的优先级Seconds_Behind_Master最小的从库优先级最高。配置化优先级在Keepalived配置中为不同从库设置不同的基础priority再通过检查脚本的weight进行调整。例如同机房从库优先级高于跨机房从库。在实际配置中可以将这些因素综合成一个分数最终通过脚本的返回值或修改Keepalived的priority动态调整来实现。5.3 与专业高可用工具的对比与选型我们的方案本质上是“Keepalived 自定义脚本”它的优点是轻量、透明、可控性强适合对运维有较强把控能力的团队。但它也有明显的缺点功能有限只实现了故障转移Failover缺乏自动的故障恢复Failback、主从拓扑自动重构、在线切换等高级功能。运维复杂度脚本的逻辑需要自己开发和维护可靠性需要经过充分测试。监控与可视化需要自行搭建完善的监控来跟踪脚本状态、复制延迟、半同步状态等。因此对于更复杂或要求更高的场景可以考虑成熟的第三方工具Orchestrator功能非常强大支持自动故障检测、恢复、拓扑可视化、中间件支持等是当前MySQL高可用领域的热门选择。MHA (Master High Availability)经典工具自动化程度高但近年来活跃度下降。ProxySQL Group Replication利用MySQL组复制InnoDB Cluster提供内置的高可用和自动选主再通过ProxySQL实现读写分离和连接路由是一套更现代、更集成的方案。选择自研方案还是成熟工具取决于团队的运维能力、业务对RTO恢复时间目标/RPO恢复点目标的要求以及技术栈的整体规划。5.4 监控与告警让运维睡个安稳觉再好的自动化方案也离不开监控。对于这套方案必须监控以下核心指标Keepalived状态各节点的VRRP状态MASTER/BACKUP。VIP漂移历史记录VIP切换的时间点和原因。MySQL健康检查脚本退出码非0退出必须立即告警。半同步复制状态Rpl_semi_sync_master_status,Rpl_semi_sync_slave_status。复制延迟Seconds_Behind_Master设置不同级别的阈值如10s警告30s严重。GTID增长情况监控主从的GTID集合差异。将这些指标接入PrometheusGrafana或Zabbix等监控系统并配置相应的告警规则如企业微信、钉钉、短信才能确保在出现问题时运维团队能第一时间被通知甚至在自动切换发生前就介入处理潜在风险。这套“半同步复制Keepalived第三方逻辑判断”的方案是我在经历了多次“傻切换”的教训后总结出的一套务实且有效的改进方案。它不是在否定经典而是在经典之上增加了至关重要的“数据逻辑层”。它告诉我们高可用的核心不仅仅是“可用”更是“数据正确的可用”。在实际部署时请务必在测试环境进行充分的故障演练摸清各种边界情况下的行为并根据自己业务的特点调整脚本的判断逻辑和阈值。只有这样才能真正让数据库层的高可用成为业务稳定运行的坚实后盾。