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

25 阅读10分钟

在生产环境中,数据库单点故障是最大的噩梦之一。本文将从最基础的 MariaDB 主从复制出发,逐步演进到 MySQL 双主同步,最终搭建一套基于 Keepalived + 双主架构的完整高可用方案。所有配置均来自生产环境实战验证,可直接落地使用。

环境说明:

角色主机名IP数据库
Masterrhel-efserver192.168.1.25MariaDB / MySQL
Slave / Backuprhel-efserver-bak192.168.1.28MariaDB / MySQL
VIP192.168.1.100Keepalived 虚拟漂移 IP

一、MariaDB 主从复制

MariaDB 是 MySQL 的分支,复制原理完全一致:Master 将数据变更写入二进制日志(Binlog),Slave 通过 I/O 线程读取 Binlog 写入中继日志(Relay Log),再由 SQL 线程重放完成同步。

1. Master 节点配置

编辑 MariaDB 服务配置文件,开启 Binlog 并设置唯一的 server-id

# 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 上创建专用的复制用户,仅授予最小权限:

-- 创建用户 repl,允许从 rhel-efserver-bak 连接
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'rhel-efserver-bak' IDENTIFIED BY 'Ins@1234';

3. 记录 Master 状态

MariaDB [(none)]> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 |      655 |              | mysql            |
+------------------+----------+--------------+------------------+

提示:  记下 File 和 Position 的值,Slave 连接时需要用到。

4. Slave 节点配置

# nano /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
server-id = 2
log_bin = mysql-bin
binlog_ignore_db = mysql

5. 配置并启动复制

指定 Master 的连接信息并启动同步进程:

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. 验证复制状态

# 查询状态
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 即表示复制正常运行:

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 数据的一致性快照:

mysqldump -uroot -pIns@1234 --single-transaction --all-databases > /var/lib/mysql/efab.sql

2. 创建复制用户

CREATE USER 'repl'@'%' IDENTIFIED BY 'Ins@1234';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

提示:  生产环境中建议使用 mysql_native_password 认证插件以兼容旧客户端,或配置 SSL 加密复制通道。

3. 从库导入数据

将主库的全量备份导入从库:

mysql -uroot -p < /var/lib/mysql/efab-bak.sql

4. 启动复制

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. 架构示意

        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)

[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)

[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 同理):

-- 在 192.168.1.25 上执行
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'Ins@1234';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

Step 2:互相指定对方为 Master 并启动复制:

-- 在 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 时执行:检查同步延迟,延迟合格后解除只读,接管写操作。

#!/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 时执行:立即开启只读,防止脑裂写入。

#!/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):

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):

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. 故障转移流程

┌─────────────────────────────────────────────────────┐
│                   正常状态                           │
│  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.shA 恢复后):开启只读           │
└─────────────────────────────────────────────────────┘
                      ▼
┌─────────────────────────────────────────────────────┐
│                   恢复后状态                         │
│  Old Master (.25)         New Master (.28) ←── VIP  │
│  read_only=1 (只读)       read_only=0 (读写)         │
└─────────────────────────────────────────────────────┘

五、用户管理与认证

生产环境中,MySQL 8.x 默认使用 caching_sha2_password 认证,如果应用驱动不兼容,需要手动切换认证方式:

-- 修改现有用户的认证插件为 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 的要求,选择最适合的方案,而不是最复杂的方案。