MySQL 高可用方案眾多,常見的有 Keepalived+雙主、MHA、MMM、Heartbeat+DRBD、PXC 及 Galera Cluster。對於中小型企業,Keepalived + MySQL 雙主(單活模式)以其架構簡單、維護成本低、故障切換快等優勢,成為首選方案。本文將詳細記錄基於 CentOS 7 環境,利用 Keepalived 實現 MySQL 雙主高可用的完整部署流程、腳本邏輯及生產環境優化建議。
| 角色 | 主機 IP | 主機名 | 作業系統 | 軟體版本 |
|---|---|---|---|---|
| VIP (虛擬 IP) | 192.168.244.10 | - | - | - |
| Master1 (主庫) | 192.168.244.145 | master1 | CentOS 7.1 | MySQL 5.6.26, Keepalived v1.2.13 |
| Master2 (備庫) | 192.168.244.146 | master2 | CentOS 7.1 | MySQL 5.6.26, Keepalived v1.2.13 |
核心架構說明: 採用「雙主單活」模式,Master1 為主服務節點(Priority 100),Master2 為備用節點。正常情況下 VIP 綁定在 Master1,Master2 設置 read_only=1 拒絕寫入。當 Master1 故障時,Keepalived 將 VIP 漂移至 Master2,並通過腳本將其設為可讀寫;主庫恢復後,需人工干預或腳本協助將 VIP 切回,避免頻繁漂移造成數據不一致風險。
分別編輯兩台主機的 /etc/my.cnf,關鍵參數如下:
# Master1 (192.168.244.145) [mysqld] log-bin=mysql-bin server-id=1 log_slave_updates=1 # Master2 (192.168.244.146) [mysqld] log-bin=mysql-bin server-id=2 log_slave_updates=1 read_only=1 # 備庫預設唯讀,防止誤寫入
參數解析: log_slave_updates=1 確保從庫收到的 Binlog 會寫入自身的 Binlog,實現 A->B->A 的級聯傳遞,這是雙主架構的基石。
# 在 Master1 上執行 CREATE USER 'repl'@'192.168.244.146' IDENTIFIED BY 'mysql'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.244.146'; # 在 Master2 上執行 CREATE USER 'repl'@'192.168.244.145' IDENTIFIED BY 'mysql'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.244.145';
由于是全新搭建,無需鎖表導出數據,直接根據 SHOW MASTER STATUS 輸出指定位置:
# Master1 指向 Master2 CHANGE MASTER TO MASTER_HOST='192.168.244.146', MASTER_USER='repl', MASTER_PASSWORD='mysql', MASTER_LOG_FILE='mysql-bin.000004', MASTER_LOG_POS=64729; # Master2 指向 Master1 CHANGE MASTER TO MASTER_HOST='192.168.244.145', MASTER_USER='repl', MASTER_PASSWORD='mysql', MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=68479;
分別執行 START SLAVE;,通過 SHOW SLAVE STATUS\G 確認 Slave_IO_Running: Yes 且 Slave_SQL_Running: Yes 即雙主復制建立成功。
yum install -y keepalived
編輯 /etc/keepalived/keepalived.conf,核心採用 BACKUP + nopreempt (非搶佔) 模式,配合優先級區分主備:
vrrp_script chk_mysql {
script "/etc/keepalived/check_mysql.sh"
interval 30
}
vrrp_instance VI_1 {
state BACKUP
interface eno16777736 # 根據實際網卡名修改
virtual_router_id 51
priority 100 # 高優先級,作為主節點
advert_int 1
nopreempt # 關鍵:防止主庫恢復後自動搶佔 VIP,減少切換風險
authentication {
auth_type PASS
auth_pass 1111
}
track_script {
chk_mysql
}
virtual_ipaddress {
192.168.244.10/24
}
}
/etc/keepalived/check_mysql.sh 邏輯:連通性檢查 -> 服務狀態判斷 -> 重試機制 -> 強制降級 (read_only + kill 連接 + 停止 keepalived)。
#!/bin/bash
# 防止腳本并发执行
if [ `ps -ef|grep -w "$0"|grep -v "grep"|wc -l` -gt 2 ];then exit 0; fi
mysql_con='mysql -uroot -p123456'
error_log="/etc/keepalived/logs/check_mysql.err"
function excute_query { ${mysql_con} -e "select 1;" 2>> ${error_log}; }
function service_error {
echo "`date '+%F %H:%M:%S'` -----mysql service error,stop keepalived-----" >> ${error_log}
service keepalived stop &>> ${error_log}
echo "DB1 keepalived stopped" | mail -s "Alert: DB1 keepalived stopped" admin@example.com 2>> ${error_log}
}
function query_error {
echo "`date '+%F %H:%M:%S'` -----query error, retry after 30s-----" >> ${error_log}
sleep 30
excute_query
if [ $? -ne 0 ]; then
echo "`date '+%F %H:%M:%S'` -----still failing, set read_only=1 & kill connections-----" >> ${error_log}
${mysql_con} -e "set global read_only = 1;" 2>> ${error_log}
# 批量 kill 正在執行的查詢線程
${mysql_con} -e 'select concat("kill ",id,";") from information_schema.PROCESSLIST where command in ("Query","Execute") into outfile "/tmp/kill.sql";'
${mysql_con} -e "source /tmp/kill.sql"
sleep 2
service keepalived stop &>> ${error_log}
echo "DB1 keepalived stopped" | mail -s "Alert: DB1 keepalived stopped" admin@example.com 2>> ${error_log}
fi
}
excute_query
if [ $? -ne 0 ]; then
service mysqld status &>/dev/null
if [ $? -ne 0 ]; then service_error; else query_error; fi
fi
Master2 優先級較低,不啟用 track_script,而是通過 notify_master 腳本在成為 MASTER 時執行數據一致性檢查並開放寫入:
# /etc/keepalived/keepalived.conf (Master2)
vrrp_instance VI_1 {
state BACKUP
interface eno16777736
virtual_router_id 51
priority 90
advert_int 1
authentication { auth_type PASS; auth_pass 1111; }
notify_master /etc/keepalived/notify_master_mysql.sh # 關鍵:狀態變為 MASTER 時觸發
virtual_ipaddress { 192.168.244.10/24; }
}
notify_master_mysql.sh 核心邏輯:檢查 Slave_SQL_Running -> 等待 Relay Log 追上 Master Log (最長 10s) -> 解除 read_only -> 發送告警。
#!/bin/bash
change_log=/etc/keepalived/logs/state_change.log
mysql_con='mysql -uroot -p123456'
slave_info() {
# 解析 show slave status 關鍵指標
# $1=slave_status 獲取 IO/SQL 線程狀態
# $1=log_file,$2=pos 獲取 Master_Log_File, Read_Master_Log_Pos, Relay_Master_Log_File, Exec_Master_Log_Pos
}
action() {
${mysql_con} -e "set global read_only = 0;" 2>> $change_log
echo "DB2 promoted to MASTER" | mail -s "DB2 Promoted" admin@example.com 2>> $change_log
}
# 1. 檢查 SQL 線程
slave_info slave_status
if [ "$Slave_SQL_Running" = "Yes" ]; then
i=0
# 2. 等待從庫應用追上主庫接收位置 (防止數據丟失)
slave_info log_file pos
until [ "$Master_Log_File" = "$Relay_Master_Log_File" -a "$Read_Master_Log_Pos" = "$Exec_Master_Log_Pos" ]; do
if [ $i -lt 10 ]; then sleep 1; i=$((i+1)); slave_info log_file pos;
else echo "Wait timeout, force promote"; action; exit 0; fi
done
action
else
# SQL 線程異常,記錄日誌並強制提升 (風險較大,生產建議人工介入)
slave_info log_file pos
echo "Slave SQL Error, force promote" >> $change_log
action
fi
由於啟用了 nopreempt,主庫恢復後不會自動搶佔 VIP。需執行手動切回腳本(建議在業務低峰期操作):
read_only=1、Kill 連接、停止 Keepalived (VIP 漂回 Master1)。read_only=0 並啟動 Keepalived。#!/bin/bash # 手動切回腳本邏輯摘要 mysql_con='mysql -uroot -p123456' # 1. 鎖定 Master2 $mysql_con -e "set global read_only = 1;" # Kill Master2 連接... # 2. 檢查 Master1 同步進度 (通過 repl 用戶連 Master1 查詢) # 3. 解鎖 Master1 並啟動 keepalived ssh 192.168.244.145 'mysql -uroot -p123456 -e "set global read_only=0;" && /etc/init.d/keepalived start' # 4. 重啟 Master2 keepalived 使其進入 BACKUP 狀態 /sbin/service keepalived restart
現象: 腳本執行 service keepalived stop 但進程殘留,導致 VIP 不漂移。
優化: 停止命令後增加檢查邏輯,等待 5 秒若進程仍在則 kill -9 強制終止。
預設日誌輸出至 /var/log/messages 查閱困難。建議獨立日誌文件:
# 1. 修改 /etc/sysconfig/keepalived KEEPALIVED_OPTIONS="-D -d -S 0" # -S 0 使用 local0 設施 # 2. 修改 /etc/rsyslog.conf (CentOS 7) local0.* /var/log/keepalived.log # 3. 重啟 rsyslog systemctl restart rsyslog
本方案為 雙主單活,同一時間僅一個節點提供寫入服務,避免了雙主雙活帶來的自增 ID 衝突、數據衝突難題。雖然資源利用率較低(備庫閒置),但數據一致性有強保障,適合寫入壓力不極大、對數據正確性要求高的業務。
所有 .sh 腳本務必執行 chmod +x。未授權會導致 Master1 無法綁定 VIP,日誌報 Entering FAULT state。上線前必須進行:主庫掉電、MySQL 進程殺死、網絡分區、主庫恢復切回 全流程演練。