在生产环境中,数据库单点故障是最大的噩梦之一。本文将从最基础的 MariaDB 主从复制出发,逐步演进到 MySQL 双主同步,最终搭建一套基于 Keepalived + 双主架构的完整高可用方案。所有配置均来自生产环境实战验证,可直接落地使用。
环境说明:
| 角色 | 主机名 | IP | 数据库 |
|---|---|---|---|
| Master | rhel-efserver | 192.168.1.25 | MariaDB / MySQL |
| Slave / Backup | rhel-efserver-bak | 192.168.1.28 | MariaDB / MySQL |
| VIP | --- | 192.168.1.100 | Keepalived 虚拟漂移 IP |
一、MariaDB 主从复制
MariaDB 是 MySQL 的分支,复制原理完全一致:Master 将数据变更写入二进制日志(Binlog),Slave 通过 I/O 线程读取 Binlog 写入中继日志(Relay Log),再由 SQL 线程重放完成同步。
1. Master 节点配置
编辑 MariaDB 服务配置文件,开启 Binlog 并设置唯一的 server-id:
ini
# nano /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
server-id = 1
log_bin = mysql-bin
binlog_ignore_db = mysql
参数说明:
server-id:集群内每台节点必须唯一,建议使用 IP 尾数或递增数字。log_bin:启用二进制日志,文件名前缀为mysql-bin。binlog_ignore_db:忽略 mysql 系统库的同步,减少不必要的日志量。
修改完成后重启 MariaDB 服务。
2. 创建复制账号
在 Master 上创建专用的复制用户,仅授予最小权限:
sql
-- 创建用户 repl,允许从 rhel-efserver-bak 连接
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'rhel-efserver-bak' IDENTIFIED BY 'Ins@1234';
3. 记录 Master 状态
sql
MariaDB [(none)]> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 | 655 | | mysql |
+------------------+----------+--------------+------------------+
提示: 记下 File 和 Position 的值,Slave 连接时需要用到。
4. Slave 节点配置
ini
# nano /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
server-id = 2
log_bin = mysql-bin
binlog_ignore_db = mysql
5. 配置并启动复制
指定 Master 的连接信息并启动同步进程:
ini
CHANGE MASTER TO
MASTER_HOST='rhel-efserver',
MASTER_USER='repl',
MASTER_PASSWORD='Ins@1234';
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=655;
START SLAVE;
6. 验证复制状态
yaml
# 查询状态
show slave status\G;
MariaDB [(none)]> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: rhel-efserver
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 655
Relay_Log_File: mariadb-relay-bin.000002
Relay_Log_Pos: 954
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 655
Relay_Log_Space: 1265
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_SSL_Crl:
Master_SSL_Crlpath:
Using_Gtid: No
Gtid_IO_Pos:
Replicate_Do_Domain_Ids:
Replicate_Ignore_Domain_Ids:
Parallel_Mode: conservative
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
Slave_DDL_Groups: 2
Slave_Non_Transactional_Groups: 0
Slave_Transactional_Groups: 0
1 row inset (0.000 sec)
重点关注以下两个指标,均为 Yes 即表示复制正常运行:
yaml
Slave_IO_Running: Yes ← I/O 线程已连接 Master,正常读取 Binlog
Slave_SQL_Running: Yes ← SQL 线程正常运行,正在重放中继日志
Seconds_Behind_Master: 0 ← 从库延迟秒数,0 表示完全同步
二、MySQL 主从复制
MySQL 8.x 的主从配置与 MariaDB 基本相同,主要区别在于用户认证方式的变化。以下是核心步骤:
1. 全量数据导出(主库执行)
使用 --single-transaction 参数保证 InnoDB 数据的一致性快照:
ruby
mysqldump -uroot -pIns@1234 --single-transaction --all-databases > /var/lib/mysql/efab.sql
2. 创建复制用户
sql
CREATE USER 'repl'@'%' IDENTIFIED BY 'Ins@1234';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
提示: 生产环境中建议使用 mysql_native_password 认证插件以兼容旧客户端,或配置 SSL 加密复制通道。
3. 从库导入数据
将主库的全量备份导入从库:
css
mysql -uroot -p < /var/lib/mysql/efab-bak.sql
4. 启动复制
ini
CHANGE MASTER TO
MASTER_HOST='rhel-efserver',
MASTER_USER='repl',
MASTER_PASSWORD='Ins@1234',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=655;
START SLAVE;
三、双主(Master-Master)架构
传统主从架构中,Slave 只能读不能写,Master 故障时需要手动切换。双主架构让两台节点互为 Master,各自承担写操作,配合 Keepalived 实现自动故障转移,才是真正的生产级高可用。
1. 架构示意
scss
192.168.1.100 (VIP)
│
┌──────────┴──────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ Master A│◄──────► │ Master B│
│ .25 │ 互为主从 │ .28 │
└─────────┘ └─────────┘
│ │
read_only=0 read_only=1
(当前可写) (当前只读)
│ │
┌──┴─────────────────────┴──┐
│ Keepalived 自动切换 │
└───────────────────────────┘
2. Master A 配置(192.168.1.25)
ini
[mysqld]
# --- 基本设置 ---
server-id = 25 # 必须唯一,建议用IP尾数
port = 3306
datadir = /var/lib/mysql
socket = /var/lib/mysql/mysql.sock
# --- 复制核心配置 ---
log-bin = mysql-bin # 启用二进制日志
binlog-ignore-db = mysql
binlog_format = row # 推荐使用 ROW 格式,数据最安全
relay-log = relay-bin
log_slave_updates = 1 # 关键:从库执行完同步后也记入自己的binlog
# --- 防止主键冲突 (双主必备) ---
auto_increment_increment = 2 # 步长为2
auto_increment_offset = 1 # 初始值为1 (生成 ID 为 1, 3, 5...)
# --- 高可用与安全设置 ---
read_only = 0 # 主库初始为可读写
skip_name_resolve = 1 # 禁用DNS解析,提高连接速度
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
核心参数解析:
log_slave_updates = 1:双主架构的关键!没有它,A 的变更通过复制到 B 后不会写入 B 的 Binlog,无法再同步回 A。auto_increment_increment = 2+auto_increment_offset:两台节点分别生成奇数 ID(1, 3, 5...)和偶数 ID(2, 4, 6...),从根本上避免自增主键冲突。
3. Master B 配置(192.168.1.28)
ini
[mysqld]
# --- 基本设置 ---
server-id = 28 # 必须唯一
port = 3306
datadir = /var/lib/mysql
socket = /var/lib/mysql/mysql.sock
# --- 复制核心配置 ---
log-bin = mysql-bin
binlog-ignore-db = mysql
binlog_format = row
relay-log = relay-bin
log_slave_updates = 1 # 关键:允许数据级联同步
# --- 防止主键冲突 (双主必备) ---
auto_increment_increment = 2 # 步长为2
auto_increment_offset = 2 # 初始值为2 (生成 ID 为 2, 4, 6...)
# --- 高可用与安全设置 ---
read_only = 1 # 备库初始设为只读,由 Keepalived 脚本控制开关
skip_name_resolve = 1
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
4. 建立互为主从关系
Step 1:在两台节点分别创建复制用户(以 1.25 为例,1.28 同理):
sql
-- 在 192.168.1.25 上执行
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'Ins@1234';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
Step 2:互相指定对方为 Master 并启动复制:
ini
-- 在 192.168.1.28 上执行:指定 1.25 为自己的 Master
CHANGE MASTER TO
MASTER_HOST='192.168.1.25',
MASTER_USER='repl',
MASTER_PASSWORD='Ins@1234';
START SLAVE;
-- 在 192.168.1.25 上执行:指定 1.28 为自己的 Master
CHANGE MASTER TO
MASTER_HOST='192.168.1.28',
MASTER_USER='repl',
MASTER_PASSWORD='Ins@1234';
START SLAVE;
5. 验证双主状态
在两台节点上分别执行 SHOW SLAVE STATUS\G,确认 Slave_IO_Running 和 Slave_SQL_Running 均为 Yes。
四、Keepalived 自动故障转移
光有双主还不够------应用需要通过一个固定的 VIP 访问数据库,主节点挂掉时 VIP 自动漂移到备节点。这就是 Keepalived 的职责。
1. 故障切换脚本
to_master.sh :节点升为 Master 时执行:检查同步延迟,延迟合格后解除只读,接管写操作。
bash
#!/bin/bash
# --- 配置项 ---
MYSQL_USER="root"
MYSQL_PASS="Ins@1234"
MYSQL_CONN="mysql -u${MYSQL_USER} -p${MYSQL_PASS} -e"
MAX_DELAY=5 # 允许的最大主从延迟秒数
echo"$(date) [INFO]: Keepalived 状态切换为 MASTER,正在检查同步状态..." >> /var/log/keepalived_mysql.log
# 1. 检查 MySQL 服务是否存活
if ! mysqladmin -u${MYSQL_USER} -p${MYSQL_PASS} ping > /dev/null 2>&1; then
echo"$(date) [ERROR]: MySQL 服务未运行,无法接管!" >> /var/log/keepalived_mysql.log
exit 1
fi
# 2. 等待数据同步完成(针对双主架构恢复后的数据补齐)
RETRY=0
while [ $RETRY -lt 10 ]; do
DELAY=$($MYSQL_CONN"SHOW SLAVE STATUS\G" | grep "Seconds_Behind_Master" | awk '{print $2}')
if [[ "$DELAY" == "NULL" ]] || [[ -z "$DELAY" ]]; then
echo"$(date) [WARN]: Slave 状态异常或未连接,请检查主从复制状态。" >> /var/log/keepalived_mysql.log
break
elif [ "$DELAY" -le $MAX_DELAY ]; then
echo"$(date) [INFO]: 数据同步完成 (延迟: ${DELAY}s),准备开启读写。" >> /var/log/keepalived_mysql.log
break
else
echo"$(date) [INFO]: 等待同步中,当前延迟: ${DELAY}s..." >> /var/log/keepalived_mysql.log
sleep 2
let RETRY++
fi
done
# 3. 解除只读状态,正式接管业务
$MYSQL_CONN"SET GLOBAL read_only = 0; SET GLOBAL super_read_only = 0;"
if [ $? -eq 0 ]; then
echo"$(date) [SUCCESS]: 成功切换为读写模式 (Master)。" >> /var/log/keepalived_mysql.log
else
echo"$(date) [ERROR]: 切换读写模式失败!" >> /var/log/keepalived_mysql.log
exit 1
fi
to_backup.sh:节点降为 Backup 时执行:立即开启只读,防止脑裂写入。
bash
#!/bin/bash
MYSQL_USER="root"
MYSQL_PASS="Ins@1234"
echo "$(date) [INFO]: Keepalived 状态切换为 BACKUP,开启只读模式。" >> /var/log/keepalived_mysql.log
mysql -u${MYSQL_USER} -p${MYSQL_PASS} -e "SET GLOBAL read_only = 1; SET GLOBAL super_read_only = 1;"
安全机制: super_read_only 连拥有 SUPER 权限的用户也只能读了,彻底杜绝脑裂写入的可能。
2. Keepalived 配置
Master 节点(192.168.1.25):
perl
global_defs {
router_id m01
vrrp_skip_check_adv_addr
# vrrp_strict # 注释掉,否则在某些网络环境下VIP无法ping通
vrrp_garp_interval 0
vrrp_gna_interval 0
}
# 脚本1:检查 Nginx 和 Broker 服务
vrrp_script check_services {
script "pidof nginx && systemctl is-active dcv-session-manager-broker"
interval 2
weight -20 # 失败则权重减 20
}
# 脚本2:检查 MySQL 服务状态
vrrp_script check_mysql {
script "mysqladmin ping -u root -p'Ins@1234'"
interval 2
weight -30 # 失败则权重减 30
}
vrrp_instance VI_1 {
state MASTER # 主节点初始状态
interface eth2 # 请确认网卡名称正确(如 ens33, eth0 等)
virtual_router_id 51 # 主备必须一致
priority 100 # 主节点优先级(需高于备节点)
advert_int 1
authentication {
auth_type PASS
auth_pass dcv_ha_pwd
}
virtual_ipaddress {
192.168.1.100 # 虚拟IP (VIP)
}
track_script {
check_services
check_mysql
}
# 状态切换触发脚本
notify_master "/etc/keepalived/to_master.sh"# 变为MASTER时:关闭只读,开启写
notify_backup "/etc/keepalived/to_backup.sh"# 变为BACKUP时:开启只读
notify_fault "/etc/keepalived/to_backup.sh"# 发生故障时:强制只读
}
Backup 节点(192.168.1.28):
perl
global_defs {
router_id m02 # 建议与主节点不同
vrrp_skip_check_adv_addr
# vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
}
vrrp_script check_services {
script "pidof nginx && systemctl is-active dcv-session-manager-broker"
interval 2
weight -20
}
vrrp_script check_mysql {
script "mysqladmin ping -u root -p'Ins@1234'"
interval 2
weight -30
}
vrrp_instance VI_1 {
state BACKUP # 备节点初始状态
interface eth2 # 必须与物理网卡一致
virtual_router_id 51
priority 90 # 优先级低于主节点
advert_int 1
authentication {
auth_type PASS
auth_pass dcv_ha_pwd
}
virtual_ipaddress {
192.168.1.100
}
track_script {
check_services
check_mysql
}
# 状态切换触发脚本
notify_master "/etc/keepalived/to_master.sh"
notify_backup "/etc/keepalived/to_backup.sh"
notify_fault "/etc/keepalived/to_backup.sh"
}
3. 故障转移流程
scss
┌─────────────────────────────────────────────────────┐
│ 正常状态 │
│ Master A (.25) ←── VIP ──→ Backup B (.28) │
│ read_only=0 (读写) read_only=1 (只读) │
└─────────────────────────────────────────────────────┘
│
Master A 故障
▼
┌────────────────────────────────────────────────────┐
│ 故障转移 │
│ 1. check_mysql 检测失败,priority 100-30 = 70 │
│ 2. Backup B priority(90) > 70,接管 VIP │
│ 3. 触发 to_master.sh:检查延迟 → 解除只读 │
│ 4. 触发 to_backup.sh(A 恢复后):开启只读 │
└─────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ 恢复后状态 │
│ Old Master (.25) New Master (.28) ←── VIP │
│ read_only=1 (只读) read_only=0 (读写) │
└─────────────────────────────────────────────────────┘
五、用户管理与认证
生产环境中,MySQL 8.x 默认使用 caching_sha2_password 认证,如果应用驱动不兼容,需要手动切换认证方式:
sql
-- 修改现有用户的认证插件为 mysql_native_password
ALTER USER 'dcvbk'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Ins@1234';
-- 创建新用户并授权
CREATE USER 'dcvbk'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Ins@1234';
GRANT ALL PRIVILEGES ON dcvdb.* TO 'dcvbk'@'localhost';
-- 验证用户信息
SELECT user, host, plugin FROM mysql.user WHERE user = 'dcvbk';
SHOW GRANTS FOR 'dcvtest'@'%';
六、总结
本文覆盖了 MySQL / MariaDB 复制架构从入门到生产的完整路径:
| 方案 | 适用场景 | 优势 | 不足 |
|---|---|---|---|
| MariaDB 主从 | 读写分离、报表查询 | 配置简单,上手快 | 手动故障切换 |
| MySQL 主从 | 数据备份、读扩展 | 8.x 原生支持 GTID | 手动故障切换 |
| 双主 + Keepalived | 生产级高可用 | 自动故障转移,零停机 | 架构复杂度更高 |
生产部署建议:
- 网络层面: 主备节点部署在同一机房、同网段,尽量降低网络延迟
- 监控告警: 务必监控
Seconds_Behind_Master、Slave_IO_Running、Slave_SQL_Running三个指标 - 定期演练: 每季度至少一次手动故障切换演练,确保预案有效
- 数据校验: 使用
pt-table-checksum/pt-table-sync(Percona Toolkit)定期校验主备数据一致性 - 备份兜底: 主从复制不是备份方案!仍需配合定期全量备份 + Binlog 增量备份
最后提醒: 没有银弹------双主架构解决了单点故障,但引入了脑裂和数据一致性的新挑战。根据业务对 RPO / RTO 的要求,选择最适合的方案,而不是最复杂的方案。