MySQL主从复制与Redis哨兵高可用集群实战

作者:没有四次元口袋的蓝胖

日期:2026-09-17

标签:MySQL, Redis, 高可用集群, 主从复制, 哨兵模式


写在前面

这是一篇 MySQL 主从复制 + Redis 哨兵高可用集群的实战记录。基于 Docker 容器技术,分别搭建了 MySQL 一主两从(GTID 模式)和 Redis 主从+哨兵集群,并验证了自动故障转移机制。


一、项目背景

单机数据库存在三个致命问题:

  1. 单点故障:数据库挂了 = 整个系统瘫痪
  2. 读写瓶颈:所有读写请求压在一台机器上
  3. 数据风险:硬盘坏了数据就没了

解决方案

组件 方案 解决的问题
MySQL 主从复制(一主两从,GTID 模式) 读写分离 + 数据冗余
Redis 主从 + 哨兵(Sentinel) 高可用 + 自动故障转移

二、MySQL 主从复制原理

2.1 replication 工作流程

复制代码
                    写操作
                      │
                      ▼
              ┌──────────────┐
              │   Master     │  写入 binlog(二进制日志)
              │  (主库)       │
              └──────┬───────┘
                     │
              dump thread 发送 binlog 事件
                     │
          ┌──────────┼──────────┐
          ▼                     ▼
   ┌──────────────┐     ┌──────────────┐
   │   Slave 1    │     │   Slave 2    │
   │  (从库1)      │     │  (从库2)      │
   └──────────────┘     └──────────────┘
   
   每个从库有两个线程:
   - IO 线程:从主库拉取 binlog → 写入 relay log
   - SQL 线程:读取 relay log → 重放 SQL

2.2 两种复制模式对比

对比项 传统模式(基于文件名+偏移量) GTID 模式(推荐)
定位方式 MASTER_LOG_FILE + MASTER_LOG_POS GTID(全局唯一事务ID)
配置复杂度 高(需要精确计算偏移量) 低(自动定位)
故障恢复 手动找偏移量,容易出错 自动找到断点继续同步
数据一致性 依赖偏移量正确性 每个事务有唯一 ID,更可靠

💡 结论:生产环境直接用 GTID 模式,传统模式了解一下原理就行。

2.3 GTID 是什么?

GTID = Global Transaction Identifier,格式为 UUID:NUMBER

  • UUID:MySQL 实例的唯一标识(server_uuid)
  • NUMBER:该实例上执行的事务序号(从 1 开始递增)

例如:a1b2c3d4-1234-5678-9012-e5f6g7h8i9j0:42 表示某个 MySQL 实例上第 42 个事务。


三、MySQL 一主两从集群搭建

3.1 环境规划

角色 容器名 端口映射 server-id
主库 mysql-master 3307:3306 1
从库1 mysql-slave1 3308:3306 2
从库2 mysql-slave2 3309:3306 3

3.2 主库配置(my.cnf)

ini 复制代码
[mysqld]
server-id=1
log-bin=mysql-bin
binlog-format=ROW
gtid-mode=ON
enforce-gtid-consistency=TRUE

# 允许从库连接
# 注意:生产环境要限定具体 IP

3.3 从库配置(my.cnf)

ini 复制代码
[mysqld]
server-id=2    # 从库1用2,从库2用3(每个实例必须唯一)
log-bin=mysql-bin
relay-log=relay-bin
gtid-mode=ON
enforce-gtid-consistency=TRUE

# 从库设置为只读(防止误写)
read-only=1

3.4 Docker Compose 编排

yaml 复制代码
version: '3.8'

services:
  mysql-master:
    image: mysql:5.7
    container_name: mysql-master
    environment:
      MYSQL_ROOT_PASSWORD: root123
    ports:
      - "3307:3306"
    volumes:
      - ./master/my.cnf:/etc/mysql/conf.d/my.cnf
      - master-data:/var/lib/mysql
    networks:
      - mysql-cluster

  mysql-slave1:
    image: mysql:5.7
    container_name: mysql-slave1
    environment:
      MYSQL_ROOT_PASSWORD: root123
    ports:
      - "3308:3306"
    volumes:
      - ./slave1/my.cnf:/etc/mysql/conf.d/my.cnf
      - slave1-data:/var/lib/mysql
    networks:
      - mysql-cluster
    depends_on:
      - mysql-master

  mysql-slave2:
    image: mysql:5.7
    container_name: mysql-slave2
    environment:
      MYSQL_ROOT_PASSWORD: root123
    ports:
      - "3309:3306"
    volumes:
      - ./slave2/my.cnf:/etc/mysql/conf.d/my.cnf
      - slave2-data:/var/lib/mysql
    networks:
      - mysql-cluster
    depends_on:
      - mysql-master

volumes:
  master-data:
  slave1-data:
  slave2-data:

networks:
  mysql-cluster:
    driver: bridge

3.5 配置主从关系

步骤1:在主库创建复制专用账户

sql 复制代码
-- 登录主库
mysql -h127.0.0.1 -P3307 -uroot -proot123

-- 创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl123';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

步骤2:在从库配置主库信息(GTID 模式)

sql 复制代码
-- 登录从库1
mysql -h127.0.0.1 -P3308 -uroot -proot123

-- 配置主库信息
CHANGE MASTER TO
  MASTER_HOST='mysql-master',
  MASTER_PORT=3306,
  MASTER_USER='repl',
  MASTER_PASSWORD='repl123',
  MASTER_AUTO_POSITION=1;    -- GTID 模式:自动定位

-- 启动复制
START SLAVE;

-- 查看复制状态
SHOW SLAVE STATUS\G

关键状态检查

  • Slave_IO_Running: Yes → IO 线程正常(能从主库拉 binlog)
  • Slave_SQL_Running: Yes → SQL 线程正常(能重放 SQL)
  • Seconds_Behind_Master: 0 → 没有延迟

从库2 的操作完全相同,只是 server-id=3,端口用 3309。

3.6 验证主从同步

sql 复制代码
-- 在主库创建测试数据
mysql -h127.0.0.1 -P3307 -uroot -proot123
CREATE DATABASE test_db;
USE test_db;
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO users VALUES (1, '张三'), (2, '李四');

-- 在从库验证
mysql -h127.0.0.1 -P3308 -uroot -proot123
USE test_db;
SELECT * FROM users;
-- 应该能看到:1 张三, 2 李四

3.7 🎯 面试高频问题

Q:MySQL 主从复制的三个线程分别是什么?

线程 运行位置 作用
Dump Thread 主库 将 binlog 事件发送给从库
IO Thread 从库 从主库接收 binlog → 写入 relay log
SQL Thread 从库 读取 relay log → 重放 SQL 语句

Q:主从延迟怎么解决?

  1. 业务层面:写操作后立即读走主库(强制读主)
  2. 半同步复制:主库等待至少一个从库确认收到 binlog 后才返回(折中方案)
  3. 并行复制 :MySQL 5.7+ 支持 slave_parallel_workers,多个 SQL 线程并行重放
  4. MGR(Group Replication):MySQL 5.7.17+ 原生支持的多主复制方案

Q:binlog 的三种格式有什么区别?

格式 记录内容 优点 缺点
STATEMENT 原始 SQL 语句 日志量小 不安全(NOW()、UUID() 等函数导致数据不一致)
ROW 数据行的变更 最安全,精确记录行变化 日志量大
MIXED 默认 STATEMENT,不安全时自动切 ROW 折中 仍有风险

💡 生产环境推荐用 ROW 格式。


四、Shell 脚本自动化配置主从

手动在每个从库执行 SQL 太麻烦,写个脚本一键搞定:

bash 复制代码
#!/bin/bash
# setup_replication.sh - 自动化配置 MySQL 主从关系

MASTER_HOST="mysql-master"
MASTER_PORT=3306
REPL_USER="repl"
REPL_PASS="repl123"
ROOT_PASS="root123"

echo "===== Step 1: 在主库创建复制账户 ====="
docker exec mysql-master mysql -uroot -p${ROOT_PASS} -e "
CREATE USER IF NOT EXISTS '${REPL_USER}'@'%' IDENTIFIED BY '${REPL_PASS}';
GRANT REPLICATION SLAVE ON *.* TO '${REPL_USER}'@'%';
FLUSH PRIVILEGES;
"

echo "===== Step 2: 配置从库1 ====="
docker exec mysql-slave1 mysql -uroot -p${ROOT_PASS} -e "
STOP SLAVE;
CHANGE MASTER TO
  MASTER_HOST='${MASTER_HOST}',
  MASTER_PORT=${MASTER_PORT},
  MASTER_USER='${REPL_USER}',
  MASTER_PASSWORD='${REPL_PASS}',
  MASTER_AUTO_POSITION=1;
START SLAVE;
"

echo "===== Step 3: 配置从库2 ====="
docker exec mysql-slave2 mysql -uroot -p${ROOT_PASS} -e "
STOP SLAVE;
CHANGE MASTER TO
  MASTER_HOST='${MASTER_HOST}',
  MASTER_PORT=${MASTER_PORT},
  MASTER_USER='${REPL_USER}',
  MASTER_PASSWORD='${REPL_PASS}',
  MASTER_AUTO_POSITION=1;
START SLAVE;
"

echo "===== Step 4: 验证复制状态 ====="
for SLAVE in mysql-slave1 mysql-slave2; do
    echo "--- ${SLAVE} ---"
    docker exec ${SLAVE} mysql -uroot -p${ROOT_PASS} -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master"
done

echo "===== 主从配置完成 ====="

💡 这就是运维自动化的价值 ------ 把重复操作封装成脚本,减少人为错误。


五、Redis 主从 + 哨兵(Sentinel)集群

5.1 为什么需要哨兵?

Redis 主从复制的问题:主节点挂了,从节点不会自动升级为主节点,需要人工介入。

哨兵模式解决的问题:自动故障检测 + 自动故障转移(Failover)

5.2 哨兵的工作原理

复制代码
              ┌─────────────┐
              │  Sentinel 1 │
              └──────┬──────┘
                     │ 监控 + 投票
              ┌──────┼──────┐
              ▼             ▼
       ┌──────────┐  ┌──────────┐
       │Sentinel 2│  │Sentinel 3│
       └──────────┘  └──────────┘
              │
              │ 监控
       ┌──────┼──────┐
       ▼      ▼      ▼
  ┌────────┐┌────────┐┌────────┐
  │Master  ││Slave 1 ││Slave 2 │
  │(主节点) ││(从节点) ││(从节点) │
  └────────┘└────────┘└────────┘
  
  故障转移流程:
  1. 哨兵发现主节点不可达 → 标记为"主观下线"(SDOWN)
  2. 超过 quorum 个哨兵同意 → 标记为"客观下线"(ODOWN)
  3. 哨兵投票选出一个 Leader 哨兵
  4. Leader 哨兵从从节点中选一个升级为主节点
  5. 通知其他从节点跟随新的主节点
  6. 通知客户端新的主节点地址

5.3 环境规划

角色 容器名 端口映射
Redis 主节点 redis-master 6380:6379
Redis 从节点1 redis-slave1 6381:6379
Redis 从节点2 redis-slave2 6382:6379
哨兵1 sentinel1 26379:26379
哨兵2 sentinel2 26380:26379
哨兵3 sentinel3 26381:26379

5.4 Redis 主从配置

主节点 redis.conf

conf 复制代码
# 绑定地址
bind 0.0.0.0

# 开启 AOF 持久化
appendonly yes

# 主节点密码
requirepass redis123

从节点 redis.conf

conf 复制代码
bind 0.0.0.0
appendonly yes
requirepass redis123

# 指定主节点(从节点配置)
replicaof redis-master 6379
masterauth redis123

5.5 哨兵配置(sentinel.conf)

conf 复制代码
# 监控主节点,quorum=2(至少2个哨兵同意才判定下线)
sentinel monitor mymaster redis-master 6379 2

# 主节点下线判定时间(毫秒)
sentinel down-after-milliseconds mymaster 5000

# 故障转移超时时间
sentinel failover-timeout mymaster 60000

# 故障转移时,最多同时同步的从节点数
sentinel parallel-syncs mymaster 1

# 哨兵密码(如果 Redis 开启了密码)
sentinel auth-pass mymaster redis123

关键参数解析

参数 含义 建议值
quorum 判定主节点下线需要的哨兵数 哨兵总数的过半数(如 3 个哨兵设 2)
down-after-milliseconds 多久没响应判定为下线 5000(5秒)
failover-timeout 故障转移超时 60000(60秒)
parallel-syncs 故障转移后同时同步的从节点数 1(避免所有从节点同时同步导致服务不可用)

5.6 Docker Compose 编排

yaml 复制代码
version: '3.8'

services:
  redis-master:
    image: redis:6-alpine
    container_name: redis-master
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./redis/master/redis.conf:/usr/local/etc/redis/redis.conf
      - master-data:/data
    ports:
      - "6380:6379"
    networks:
      - redis-cluster

  redis-slave1:
    image: redis:6-alpine
    container_name: redis-slave1
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./redis/slave1/redis.conf:/usr/local/etc/redis/redis.conf
      - slave1-data:/data
    ports:
      - "6381:6379"
    networks:
      - redis-cluster
    depends_on:
      - redis-master

  redis-slave2:
    image: redis:6-alpine
    container_name: redis-slave2
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./redis/slave2/redis.conf:/usr/local/etc/redis/redis.conf
      - slave2-data:/data
    ports:
      - "6382:6379"
    networks:
      - redis-cluster
    depends_on:
      - redis-master

  sentinel1:
    image: redis:6-alpine
    container_name: sentinel1
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./redis/sentinel/sentinel.conf:/usr/local/etc/redis/sentinel.conf
    ports:
      - "26379:26379"
    networks:
      - redis-cluster
    depends_on:
      - redis-master

  sentinel2:
    image: redis:6-alpine
    container_name: sentinel2
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./redis/sentinel/sentinel2.conf:/usr/local/etc/redis/sentinel.conf
    ports:
      - "26380:26379"
    networks:
      - redis-cluster
    depends_on:
      - redis-master

  sentinel3:
    image: redis:6-alpine
    container_name: sentinel3
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./redis/sentinel/sentinel3.conf:/usr/local/etc/redis/sentinel.conf
    ports:
      - "26381:26379"
    networks:
      - redis-cluster
    depends_on:
      - redis-master

volumes:
  master-data:
  slave1-data:
  slave2-data:

networks:
  redis-cluster:
    driver: bridge

5.7 验证故障转移

步骤1:确认主从关系正常

bash 复制代码
# 查看主节点信息
docker exec redis-master redis-cli -a redis123 INFO replication

# 应该看到:
# role:master
# connected_slaves:2

步骤2:查看哨兵状态

bash 复制代码
docker exec sentinel1 redis-cli -p 26379 SENTINEL master mymaster

# 应该看到:
# name:mymaster
# status:ok
# num-slaves:2
# num-other-sentinels:2

步骤3:模拟主节点故障

bash 复制代码
# 停止主节点
docker stop redis-master

步骤4:等待 5~10 秒后检查故障转移结果

bash 复制代码
# 查看哨兵的判断
docker exec sentinel1 redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

# 返回的 IP 已经变了 → 说明新的主节点已经选出!

# 查看哪个从节点被提升为主节点
docker exec redis-slave1 redis-cli -a redis123 INFO replication
# 如果 role:master → 说明这个从节点已被提升为新主节点

步骤5:恢复旧主节点

bash 复制代码
# 重新启动旧主节点
docker start redis-master

# 旧主节点会自动变成从节点,跟随新的主节点
docker exec redis-master redis-cli -a redis123 INFO replication
# role:slave → 自动降级为从节点

5.8 🎯 常见坑点

坑1:哨兵无法判定主节点下线

原因:哨兵数量不够或 quorum 设置过大。3 个哨兵时 quorum 设为 2,如果只有 1 个哨兵能连接到主节点则无法触发故障转移。

坑2:故障转移后应用还连接旧主节点

原因:应用直连 Redis IP。

解决:应用通过哨兵获取主节点地址:

java 复制代码
// Spring Boot 配置
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=sentinel1:26379,sentinel2:26379,sentinel3:26379

坑3:sentinel.conf 被自动修改后重启失败

哨兵在故障转移过程中会修改 sentinel.conf 文件。如果文件权限不对或容器内无写权限,会导致哨兵启动失败。确保 sentinel.conf 文件权限为 666


六、整体架构图

复制代码
┌─────────────────── MySQL 集群 ───────────────────┐
│                                                    │
│   ┌──────────┐     binlog      ┌──────────┐       │
│   │  Master   │ ─────────────→ │  Slave 1  │       │
│   │ (写操作)   │               │ (读操作)   │       │
│   │ :3307     │ ─────────────→ │  Slave 2  │       │
│   └──────────┘     binlog      │ (读操作)   │       │
│                                 └──────────┘       │
└────────────────────────────────────────────────────┘

┌─────────────────── Redis 集群 ────────────────────┐
│                                                    │
│   ┌──────────┐   复制    ┌──────────┐             │
│   │  Master   │ ───────→ │  Slave 1  │             │
│   │ (主节点)   │ ───────→ │  Slave 2  │             │
│   │ :6380     │   复制    └──────────┘             │
│   └──────────┘                                     │
│        ↑ 监控         ┌──────────────┐             │
│   ┌────┴────┐         │  Sentinel ×3 │             │
│   │ 哨兵集群  │ ←────── │ (自动故障转移) │             │
│   └─────────┘         └──────────────┘             │
└────────────────────────────────────────────────────┘

七、写在最后

学习建议

  1. 主从复制原理必须理解透,不只是会配,面试会问 binlog 格式、复制线程、延迟处理等细节
  2. GTID 模式是主流,传统基于偏移量的模式了解原理即可,实操中优先用 GTID
  3. 哨兵的 quorum 参数 很关键,设置不当会导致误判或无法故障转移,建议记住公式:quorum = 哨兵数 / 2 + 1
  4. Shell 脚本是加分项,把重复的 SQL 操作封装成脚本,面试时展示这个能体现自动化运维意识
  5. 实操建议:在自己电脑上用 Docker 跑一遍,亲手做一次故障转移实验,面试时才能讲得生动

🎯 面试高频三连问

Q1:MySQL 主从复制的原理?

主库将数据变更写入 binlog,Dump Thread 将 binlog 事件发送给从库。从库的 IO Thread 接收后写入 relay log,SQL Thread 读取 relay log 并重放 SQL,实现数据同步。

Q2:Redis 哨兵模式的工作流程?

① 哨兵持续监控主从节点状态;② 主节点超时未响应 → 标记为 SDOWN(主观下线);③ 超过 quorum 个哨兵同意 → 标记为 ODOWN(客观下线);④ 哨兵投票选出 Leader;⑤ Leader 从从节点中选择一个升级为主节点;⑥ 通知其他从节点跟随新主节点。

Q3:Redis 主从复制是全量还是增量?

首次连接是全量同步(RDB 快照 + 增量命令),之后是增量同步(基于 replication offset 和 repl_backlog)。全量同步的触发条件:① 首次连接;② offset 已不在 repl_backlog 中。

相关推荐
蓝速科技2 小时前
国产化信创终端等保合规核心防护与落地方案丨蓝速科技
大数据·运维·数据库·人工智能·科技
pt10433 小时前
Cisco Splunk for AI Operations:AIOps企业实践
运维·人工智能·自动化
江屿风3 小时前
【Linux系统】【Linux进程终止等待详解与程序替换机制初体验】流食般投喂
linux·运维·笔记·系统架构·centos·unix
2601_949499943 小时前
工业 POF 器件国产化新思路:芯瑞科技DT‑2532Z 解决安华高 HFBR‑2532Z 供应链卡点
运维·网络·人工智能·科技·光模块
fanxiaohui121384 小时前
2026符合上飞机标准的充电宝推荐南孚传应,质量稳定又安全
运维·服务器·人工智能·科技
国际云,接待4 小时前
云服务器跨厂商迁移怎么尽量不停机:用 rsync、MySQL binlog 和 DNS TTL 做双阶段切换
运维·服务器·mysql
宝塔面板4 小时前
宝塔拨测正式发布:网站测速、持续监控、异常告警
运维
Doris__HE4 小时前
【元脑服务器NF8260G7-NF8260M7技术规格分享】
运维·服务器·网络·数据库·性能优化
wdfk_prog4 小时前
ROS教程06:ROS1 Node 启动与停止流程
运维·缓存·docker·容器·ros