☰
第四篇:Keepalived + MySQL 主从高可用实战
2026/10/9 2:44:06 网站建设 项目流程

开篇

数据库是高可用里最难的一环——它不像 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 主从高可用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询