第四篇:Keepalived + MySQL 主从高可用实战

开篇

数据库是高可用里最难 的一环------它不像 Web 那样无状态,主库一挂,光有 VIP 漂移还不够,还得保证新主库的数据是完整、可写的。Keepalived 本身只管"IP 漂移",所以做 MySQL 高可用,关键在健康检查脚本里把"谁才是合格主库"判断对。

本文作为 Keepalived 系列第 4 篇,从零搭建 双机 MySQL 主主/主从 + Keepalived VIP 漂移 的生产级方案,重点讲清楚:

  • 为什么 MySQL 高可用比 Nginx 复杂;
  • 健康检查脚本如何判断"主库真的能写";
  • 主库宕机后如何自动把 VIP 漂到备机并保证备机可写。

一、方案选型:三种常见的 MySQL 高可用

方案 组成 优点 缺点
Keepalived + MySQL 主从 VIP + 一主一从 + 手动/脚本提主 简单、成本低 故障后需把从库提为主库,丢少量数据
Keepalived + MySQL 双主 VIP + 两台互为主从 切换快、双向同步 需处理自增冲突、脑裂风险
MHA / Orchestrator 专业 MySQL 高可用管理器 自动化强、选主智能 部署复杂,仍需 VIP 入口

本文讲 双主(互为主从)+ Keepalived 方案------生产最常用、切换最快、可读可写。

二、架构设计

复制代码

|---|------------------------------------------------------|
| | +------------------+ |
| | | 应用程序 | |
| | | 连接 mysql://VIP | |
| | +--------+---------+ |
| | | |
| | (VIP 192.168.1.100:3306) |
| | | |
| | +-----------------+------------------+ |
| | | | |
| | +-------+--------+ +--------+-------+ |
| | | db1 (MASTER) | 主从双向同步 | db2 (BACKUP) | |
| | | 192.168.1.11 | <--------------> | 192.168.1.12 | |
| | | MySQL 8.0 | | MySQL 8.0 | |
| | +----------------+ +----------------+ |

  • db1:192.168.1.11,priority 150,默认持有 VIP(可写)。
  • db2:192.168.1.12,priority 100,默认备(也持有数据,db1 挂了接管后可写)。
  • VIP:192.168.1.100,应用只连这个 IP。
  • 同步:MySQL 原生 binlog 主从复制,双向(互为主从)。
  • 系统/软件:CentOS 7/8 + MySQL 8.0 + Keepalived 2.4.3。

核心思路:谁持有 VIP,谁对外提供可写服务。Keepalived 健康检查判定"当前主库能写则保持,不能写则让位",备机接管后经同步脚本提升为可写主库。

三、第一步:搭建 MySQL 双主复制

(本节假设 MySQL 已安装。重点看复制配置,Keepalived 部分在第四节。)

3.1 两台都开启 binlog 与 server-id

编辑 /etc/my.cnf,两台都加:

ini

复制代码

|---|---------------------------------------------------------------------|
| | [mysqld] |
| | server-id = 1 # db1 用1,db2 用2(必须不同) |
| | log-bin = mysql-bin |
| | binlog-format = ROW |
| | binlog-do-db = myapp # 要复制的库 |
| | log-slave-updates = ON # 关键:从库的binlog也记录,实现双向 |
| | auto_increment_offset = 1 # 防自增冲突 |
| | auto_increment_increment = 2 # 两台错开:db1 走 1,3,5...;db2 走 2,4,6... |

bash

复制代码
systemctl restart mysqld

3.2 创建复制账号(两台都要)

sql

复制代码

|---|---------------------------------------------------------------------|
| | CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl@123456'; |
| | GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%'; |
| | FLUSH PRIVILEGES; |

3.3 互指主从(双向复制)

在 db2 上执行(指向 db1):

sql

复制代码

|---|---------------------------------------|
| | CHANGE MASTER TO |
| | MASTER_HOST='192.168.1.11', |
| | MASTER_USER='repl', |
| | MASTER_PASSWORD='Repl@123456', |
| | MASTER_LOG_FILE='mysql-bin.000001', |
| | MASTER_LOG_POS=4; |
| | START SLAVE; |

在 db1 上执行(指向 db2):

sql

复制代码

|---|---------------------------------------|
| | CHANGE MASTER TO |
| | MASTER_HOST='192.168.1.12', |
| | MASTER_USER='repl', |
| | MASTER_PASSWORD='Repl@123456', |
| | MASTER_LOG_FILE='mysql-bin.000001', |
| | MASTER_LOG_POS=4; |
| | START SLAVE; |

MASTER_LOG_FILE/POS 需先 SHOW MASTER STATUS; 各自查看,不要照抄示例。

3.4 验证复制

sql

复制代码

|---|-----------------------------|
| | SHOW SLAVE STATUS\G |
| | -- 关键两项都应为 Yes: |
| | -- Slave_IO_Running: Yes |
| | -- Slave_SQL_Running: Yes |

写入测试数据验证双向同步:

sql

复制代码

|---|-------------------------------------------------------|
| | -- 在 db1 写 |
| | USE myapp; INSERT INTO t1(name) VALUES('from-db1'); |
| | -- 在 db2 查 |
| | SELECT * FROM myapp.t1; -- 应能看到 from-db1 |

四、第二步:配置 Keepalived 实现 VIP 漂移

4.1 健康检查脚本(核心中的核心)

脚本不仅要判断"MySQL 进程活着",还要判断"MySQL 能写、且当前是否允许对外 "。创建 /etc/keepalived/check_mysql.sh,两台一致:

bash

复制代码

|---|------------------------------------------------------------------------------------|
| | #!/bin/bash |
| | # 参数:本机允许对外提供服务时传 1,否则传 0 |
| | MYSQL_USER="root" |
| | MYSQL_PASS="Root@123456" |
| | MY_IP="$(ip -4 addr show eth0 | grep inet | awk '{print $2}' | cut -d/ -f1)" |
| | |
| | # 1. 判断 MySQL 是否可连 |
| | if ! mysqladmin ping -u$MYSQL_USER -p$MYSQL_PASS --silent > /dev/null 2>&1; then |
| | exit 1 # 连不上,判定不健康 |
| | fi |
| | |
| | # 2. 判断本机是否为"只读从库"(双主模式下由脚本决定谁可写) |
| | # - 若不希望备机对外提供写服务,可在备机上开启 read_only, |
| | # - 接管时再关闭。这里检查是否允许写: |
| | READ_ONLY=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -Nse "SELECT @@read_only") |
| | if [ "$READ_ONLY" = "1" ]; then |
| | exit 1 # 只读,不适合持有 VIP |
| | fi |
| | |
| | # 3. 可连且可写,健康 |
| | exit 0 |

双主模式下"谁可写"的控制 :默认两台都可能可写,为避免脑裂后双方同时写,通常备机平时设 read_only=ON 。db1 挂了、VIP 漂到 db2 后,由 notify_master 脚本把 db2 的 read_only 关闭,同时把 db1 提为从库。

bash

复制代码
chmod +x /etc/keepalived/check_mysql.sh

4.2 状态切换接管脚本(notify)

创建 /etc/keepalived/notify_mysql.sh,用于成为 Master 时做接管动作:

bash

复制代码

|---|--------------------------------------------------------------------------------|
| | #!/bin/bash |
| | # 参数1 = 当前角色:master / backup / fault |
| | TYPE=$1 |
| | MYSQL_USER="root" |
| | MYSQL_PASS="Root@123456" |
| | |
| | case $TYPE in |
| | master) |
| | # 成为主库:解除只读,确保可写 |
| | mysql -u$MYSQL_USER -p$MYSQL_PASS -e "SET GLOBAL read_only=OFF;" 2>/dev/null |
| | echo "$(date) become master, read_only=OFF" >> /var/log/keepalived_mysql.log |
| | ;; |
| | backup) |
| | # 降级为备库:开启只读,避免双写 |
| | mysql -u$MYSQL_USER -p$MYSQL_PASS -e "SET GLOBAL read_only=ON;" 2>/dev/null |
| | echo "$(date) become backup, read_only=ON" >> /var/log/keepalived_mysql.log |
| | ;; |
| | fault) |
| | echo "$(date) enter fault" >> /var/log/keepalived_mysql.log |
| | ;; |
| | esac |
| | exit 0 |

bash

复制代码
chmod +x /etc/keepalived/notify_mysql.sh

4.3 keepalived.conf ------ 主节点 db1

conf

复制代码

|---|----------------------------------------------------------|
| | global_defs { |
| | router_id KEEPALIVED_DB1 |
| | } |
| | |
| | vrrp_script chk_mysql { |
| | script "/etc/keepalived/check_mysql.sh" |
| | interval 2 |
| | timeout 2 |
| | weight -60 # 150-60=90 < 备100,保证让位 |
| | fall 2 |
| | rise 1 |
| | } |
| | |
| | vrrp_instance VI_DB { |
| | state MASTER |
| | interface eth0 |
| | virtual_router_id 80 # MySQL 业务独立ID,避开其他业务 |
| | priority 150 |
| | advert_int 1 |
| | authentication { |
| | auth_type PASS |
| | auth_pass Mysql88 # ≤8位且两端一致 |
| | } |
| | unicast_src_ip 192.168.1.11 |
| | unicast_peer { |
| | 192.168.1.12 |
| | } |
| | virtual_ipaddress { |
| | 192.168.1.100/24 dev eth0 label eth0:0 |
| | } |
| | track_interface { |
| | eth0 |
| | } |
| | track_script { |
| | chk_mysql |
| | } |
| | notify_master "/etc/keepalived/notify_mysql.sh master" |
| | notify_backup "/etc/keepalived/notify_mysql.sh backup" |
| | notify_fault "/etc/keepalived/notify_mysql.sh fault" |
| | } |

4.4 备节点 db2 配置

复制上面配置,改动四处:

  • router_id KEEPALIVED_DB2
  • state BACKUP
  • priority 100
  • unicast_src_ip 192.168.1.12,unicast_peer { 192.168.1.11 }

其余(virtual_router_id、auth_pass、virtual_ipaddress、track_script、notify_*)保持一致。

4.5 启动(两台)

bash

复制代码

|---|-------------------------------|
| | keepalived -t # 语法校验 |
| | systemctl start keepalived |
| | systemctl enable keepalived |
| | systemctl status keepalived |

五、验证:VIP 归属与故障转移

5.1 正常态

bash

复制代码

|---|------------------------------------------|
| | # 在两台分别执行 |
| | ip addr show eth0 | grep 192.168.1.100 |

VIP 应只在 db1 上。应用连 192.168.1.100:3306 可正常读写。

5.2 故障转移演练(重点)

场景:主库 db1 宕机/断网

bash

复制代码

|---|--------------------------|
| | # 在 db1 上模拟主库故障 |
| | systemctl stop mysqld |
| | # 或直接断网 |
| | systemctl stop network |

观察 db2:

bash

复制代码

|---|------------------------------------------------------|
| | tail -f /var/log/messages | grep -i keepalived |
| | # 应出现: |
| | # (VI_DB) Entering MASTER STATE |
| | # Sending gratuitous ARP on eth0 for 192.168.1.100 |

验证接管:

bash

复制代码

|---|--------------------------------------------------------------------------------------------------|
| | # db2 上 VIP 是否绑定 |
| | ip addr show eth0 | grep 192.168.1.100 |
| | |
| | # db2 是否已解除只读 |
| | mysql -uroot -p -e "SELECT @@read_only;" |
| | # 应返回 0 |
| | |
| | # 应用通过 VIP 能否写 |
| | mysql -h192.168.1.100 -uroot -p -e "USE myapp; INSERT INTO t1(name) VALUES('after-failover');" |

业务通过 VIP 正常写入,无缝切换。

5.3 恢复主库

bash

复制代码

|---|------------------------------|
| | # db1 恢复 |
| | systemctl start mysqld |
| | systemctl start keepalived |

注意:db1 恢复后按 priority(150)会重新抢占 VIP 。若不想来回切换,可在两台都加 nopreempt 且 state 都写 BACKUP(见系列第 3 篇)。

六、MySQL 高可用的关键注意事项(务必看完)

6.1 脑裂与双写防护

双主 + VIP 最大的风险是脑裂 :心跳断了,db1/db2 都以为自己是主,都解除只读同时写,数据冲突。

防护建议:

  • 备机平时保持 read_only=ON,只有 notify_master 时才关闭;
  • 心跳链路用独立/高可靠网络,或引入仲裁(如 consul 锁);
  • 数据库层用 auto_increment_offset/increment 错开自增,缓解冲突。

6.2 健康检查脚本决定了"高可用的含金量"

  • 只查 pgrep mysqld 不可靠(进程在但库假死)。
  • 一定要做真实连接探测 (mysqladmin ping)+ 可写判断 (@@read_only)。
  • 脚本执行超时要加 timeout,防脚本卡死导致误判。

6.3 主从延迟的隐患

MySQL 复制有延迟,VIP 漂到备机时备机数据可能落后。生产应:

  • 用 semi-sync(半同步复制)降低数据丢失;
  • 关注 SHOW SLAVE STATUS 的 Seconds_Behind_Master;
  • 对一致性要求极高场景,考虑 MHA 的"最新从库优先"选主策略。

6.4 密码安全

脚本里明文密码是隐患。生产可用:

  • 配置 MySQL 的 --defaults-extra-file 存放凭据;
  • 或用 ~/.my.cnf 并限制权限 600。

七、总结

用 Keepalived 做 MySQL 高可用,思路和 Nginx 完全一样------VIP 漂移 + 健康检查 。但数据库场景多了一层"数据一致性和可写性"的考量,这才是难点:

  1. MySQL 层:做好主从/双主复制,read_only 与自增错开。
  2. Keepalived 层:健康检查脚本判"能连 + 能写",notify_master 做"接管动作"。
  3. 运维层:把脑裂、主从延迟、密码安全当成一等公民对待。

掌握了这套,你就拥有了一套低成本、可读可写、自动切换的 MySQL 高可用方案。

本文为 Keepalived 高可用系列第 4 篇(完结)。完整系列:① Keepalived+Nginx 实战 → ② VRRP 原理篇 → ③ 配置与排障 → ④ Keepalived+MySQL 主从高可用。

相关推荐
陈年老古董1 小时前
Hive DML 语言学习笔记:数据加载、插入、导出与导入
hive·笔记·学习·mysql
麦壳饼1 小时前
INSERT INTO:向时序表写入数据
数据库·sonnetdb
七夜zippoe2 小时前
多 Agent 协作架构:Supervisor 模式——主管 Agent 调度实战
数据库·ai·架构·agent
我不会起名字3222 小时前
Redis 缓存与数据库一致性:先删缓存还是先更新库的 4 种方案
数据库·redis·缓存·一致性·延迟双删
for_ever_love__2 小时前
MySQL 锁与死锁讲透:行锁、间隙锁、Next-Key Lock 与排查方法
mysql·lock·行锁·死锁·间隙锁·排查·next-key
在繁华处2 小时前
1.2 Harness 工程:模型之外的竞争
数据库
云贝贝贝2 小时前
【无标题】TDSQL 分片键怎么选?选错等于数据倾斜加跨分片慢查询
运维·服务器·数据库·腾讯云
IvorySQL2 小时前
VACUUM FULL 之后 ROWID 就废了? IvorySQL 兼容性实测
数据库·人工智能·ai·postgresql·开源
A.说学逗唱的Coke2 小时前
【人工智能专题】PolarDB 深度解析:存算分离到 AI Lakebase,云原生数据库的架构演进与实战指南
数据库·人工智能·云原生