MySQL / MariaDB 主从复制架构实战指南

在生产环境中,数据库单点故障是最大的噩梦之一。本文将从最基础的 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            |
+------------------+----------+--------------+------------------+

提示: 记下 FilePosition 的值,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_RunningSlave_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_MasterSlave_IO_RunningSlave_SQL_Running 三个指标
  • 定期演练: 每季度至少一次手动故障切换演练,确保预案有效
  • 数据校验: 使用 pt-table-checksum / pt-table-sync(Percona Toolkit)定期校验主备数据一致性
  • 备份兜底: 主从复制不是备份方案!仍需配合定期全量备份 + Binlog 增量备份

最后提醒: 没有银弹------双主架构解决了单点故障,但引入了脑裂和数据一致性的新挑战。根据业务对 RPO / RTO 的要求,选择最适合的方案,而不是最复杂的方案。

相关推荐
用户6757049885022 小时前
【AI开发实战】从想法到上线,我用AI全栈开发了一款记账微信小程序
后端·aigc·ai编程
Moment2 小时前
作为前端,如果使用 Langgraph 实现第一个 Agent
前端·javascript·后端
神奇小汤圆2 小时前
高并发接口总被打崩?我用 ArrayBlockingQueue + 底层源码深度剖析搞定流控
后端
木易 士心2 小时前
MyBatis Plus 核心功能与用法
java·后端·mybatis
Victor3562 小时前
MongoDB(93)如何使用变更流跟踪数据变化?
后端
用户6757049885022 小时前
全网都在推 Claude Code,但只有这篇文章教你如何“真正”能用
后端·aigc·claude
Victor3562 小时前
MongoDB(94)什么是MongoDB Atlas?
后端
苏三说技术2 小时前
为什么越来越多的大厂抛弃MCP,转向CLI?
后端
Rust研习社2 小时前
Rust 写时克隆智能指针 Cow
后端·rust·编程语言