开篇
数据库是高可用里最难的一环——它不像 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 mysqld3.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.sh4.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.sh4.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_DB2state BACKUPpriority 100unicast_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 漂移 + 健康检查。但数据库场景多了一层"数据一致性和可写性"的考量,这才是难点:
- MySQL 层:做好主从/双主复制,read_only 与自增错开。
- Keepalived 层:健康检查脚本判"能连 + 能写",notify_master 做"接管动作"。
- 运维层:把脑裂、主从延迟、密码安全当成一等公民对待。
掌握了这套,你就拥有了一套低成本、可读可写、自动切换的 MySQL 高可用方案。
本文为 Keepalived 高可用系列第 4 篇(完结)。完整系列:① Keepalived+Nginx 实战 → ② VRRP 原理篇 → ③ 配置与排障 → ④ Keepalived+MySQL 主从高可用。