强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Redis 完全指南 / 10 - 主从复制

主从复制

10.1 复制概述

Redis 复制(Replication)允许一个 Redis 服务器(从节点/Slave)复制另一个 Redis 服务器(主节点/Master)的数据。主节点负责写入,从节点负责读取,实现读写分离。

复制架构

                 ┌──────────┐
                 │  Master  │  ← 写入
                 │ (主节点)  │
                 └────┬─────┘
                      │
            ┌─────────┼─────────┐
            │         │         │
      ┌─────▼───┐ ┌───▼────┐ ┌─▼───────┐
      │ Slave 1 │ │ Slave 2│ │ Slave 3 │  ← 读取
      │ (从节点)│ │(从节点) │ │ (从节点) │
      └─────────┘ └────────┘ └─────────┘

复制特性

特性说明
异步复制主节点不会等待从节点确认
单向复制数据只能从主到从
多个从节点一个主节点可以有多个从节点
链式复制从节点可以有自己的从节点
非阻塞复制期间主从节点都可以正常处理请求

10.2 配置主从复制

方式一:配置文件

# 从节点 redis.conf
replicaof 192.168.1.100 6379

# 如果主节点有密码
masterauth YourStr0ngP@ssword

# 从节点只读(推荐)
replica-read-only yes

# 从节点是否使用磁盘持久化
replica-lazy-flush no

方式二:运行时命令

# 在从节点上执行
redis-cli
> REPLICAOF 192.168.1.100 6379
OK

# 设置主节点密码
> CONFIG SET masterauth YourStr0ngP@ssword

# 取消复制(变为独立主节点)
> REPLICAOF NO ONE

方式三:Docker Compose

version: '3.8'

services:
  redis-master:
    image: redis:7.2
    container_name: redis-master
    ports:
      - "6379:6379"
    volumes:
      - ./master-data:/data
    command: redis-server --requirepass mypassword --appendonly yes
    networks:
      - redis-net

  redis-slave-1:
    image: redis:7.2
    container_name: redis-slave-1
    ports:
      - "6380:6379"
    command: redis-server --replicaof redis-master 6379 --masterauth mypassword --appendonly yes
    depends_on:
      - redis-master
    networks:
      - redis-net

  redis-slave-2:
    image: redis:7.2
    container_name: redis-slave-2
    ports:
      - "6381:6379"
    command: redis-server --replicaof redis-master 6379 --masterauth mypassword --appendonly yes
    depends_on:
      - redis-master
    networks:
      - redis-net

networks:
  redis-net:
    driver: bridge

10.3 复制原理

全量同步(Full Resynchronization)

首次连接或无法增量同步时触发:

从节点                    主节点
  │                         │
  │ REPLICAOF host port     │
  │────────────────────────→│
  │                         │
  │ PSYNC ? -1              │  ← 首次同步,offset 为 -1
  │────────────────────────→│
  │                         │
  │                         │ BGSAVE 生成 RDB
  │                         │ 记录复制缓冲区
  │                         │
  │ FULLRESYNC <runid> <offset>
  │←────────────────────────│
  │                         │
  │ (等待 RDB 文件)          │
  │←────────────────────────│  发送 RDB 文件
  │                         │
  │ 加载 RDB 到内存          │
  │                         │
  │ (等待增量数据)           │
  │←────────────────────────│  发送复制缓冲区中的增量命令
  │                         │
  │ (同步完成,开始正常复制) │

增量同步(Partial Resynchronization)

断线重连后的增量同步(Redis 2.8+):

从节点                    主节点
  │                         │
  │ (断线重连)              │
  │ PSYNC <runid> <offset>  │
  │────────────────────────→│
  │                         │ 检查 offset 是否在复制积压缓冲区中
  │                         │
  │ CONTINUE                │  ← 增量同步
  │←────────────────────────│
  │                         │
  │ (从断点处继续接收命令)   │
  │←────────────────────────│  发送 offset 之后的命令

复制积压缓冲区(Replication Backlog)

# 复制积压缓冲区大小
repl-backlog-size 256mb

# 无从节点时释放缓冲区的时间(秒,0 表示不释放)
repl-backlog-ttl 3600

缓冲区大小计算公式:

repl-backlog-size = 断线时间(秒) × 写入 QPS × 平均命令大小

# 示例:假设断线 60 秒,写入 QPS 10000,平均命令 100 字节
repl-backlog-size = 60 × 10000 × 100 = 60000000 ≈ 57 MB
# 建议设为计算值的 2 倍:114 MB

10.4 复制状态监控

# 主节点查看复制状态
redis-cli INFO replication
# role:master
# connected_slaves:2
# slave0:ip=192.168.1.101,port=6379,state=online,offset=12345,lag=0
# slave1:ip=192.168.1.102,port=6379,state=online,offset=12345,lag=1
# master_replid:abc123...
# master_repl_offset:12345
# repl_backlog_active:1
# repl_backlog_size:268435456
# repl_backlog_first_byte_offset:1
# repl_backlog_histlen:12345

# 从节点查看复制状态
redis-cli INFO replication
# role:slave
# master_host:192.168.1.100
# master_port:6379
# master_link_status:up
# master_last_io_seconds_ago:1
# master_sync_in_progress:0
# slave_read_repl_offset:12345
# slave_repl_offset:12345
# slave_priority:100

复制状态字段说明

字段说明
master_link_status主从连接状态(up/down)
master_last_io_seconds_ago上次主节点通信的秒数
master_sync_in_progress是否正在全量同步
slave_repl_offset从节点当前的复制偏移量
slave_priority从节点优先级(越小越优先被选为新主)
slave_read_only从节点是否只读

10.5 哨兵 Sentinel

Sentinel 架构

              ┌────────────┐
              │ Sentinel 1 │
              └─────┬──────┘
                    │ 监控
        ┌───────────┼───────────┐
        │           │           │
  ┌─────▼────┐ ┌───▼──────┐ ┌──▼────────┐
  │ Sentinel 2│ │ Sentinel 3│ │  Master    │
  └─────┬────┘ └───┬──────┘ └──┬─────────┘
        │           │           │
        │     监控   │     ┌────┼────────┐
        │           │     │    │        │
        │     ┌─────┼─────┼────┘        │
        │     │     │     │             │
        │  ┌──▼─────▼──┐ ┌▼─────────┐  │
        │  │  Slave 1   │ │ Slave 2  │  │
        │  └────────────┘ └──────────┘  │
        │                               │
        └───── 故障检测 + 自动故障转移 ──┘

Sentinel 功能

功能说明
监控检测主从节点是否正常运行
通知通过 Pub/Sub 通知客户端主节点变更
自动故障转移主节点不可用时自动将从节点提升为主节点
配置中心客户端连接 Sentinel 获取当前主节点地址

Sentinel 配置

# sentinel.conf

# 监控的主节点(quorum=2 表示至少 2 个 Sentinel 认为 Master 下线才触发故障转移)
sentinel monitor mymaster 192.168.1.100 6379 2

# 主节点密码
sentinel auth-pass mymaster YourStr0ngP@ssword

# 主节点无响应超时时间(毫秒)
sentinel down-after-milliseconds mymaster 5000

# 故障转移超时时间(毫秒)
sentinel failover-timeout mymaster 60000

# 并行同步的从节点数量
sentinel parallel-syncs mymaster 1

# 通知脚本(可选)
# sentinel notification-script mymaster /opt/scripts/notify.sh

# 客户端重配置脚本(可选)
# sentinel client-reconfig-script mymaster /opt/scripts/reconfig.sh

# 日志
loglevel notice
logfile /var/log/redis/sentinel.log

# Sentinel 端口
port 26379

Docker Compose 哨兵集群

version: '3.8'

services:
  redis-master:
    image: redis:7.2
    container_name: redis-master
    command: redis-server --requirepass mypassword --appendonly yes
    networks:
      - redis-net

  redis-slave-1:
    image: redis:7.2
    container_name: redis-slave-1
    command: redis-server --replicaof redis-master 6379 --masterauth mypassword --appendonly yes
    depends_on:
      - redis-master
    networks:
      - redis-net

  redis-slave-2:
    image: redis:7.2
    container_name: redis-slave-2
    command: redis-server --replicaof redis-master 6379 --masterauth mypassword --appendonly yes
    depends_on:
      - redis-master
    networks:
      - redis-net

  sentinel-1:
    image: redis:7.2
    container_name: sentinel-1
    command: >
      redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./sentinel-1.conf:/etc/redis/sentinel.conf
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
    networks:
      - redis-net

  sentinel-2:
    image: redis:7.2
    container_name: sentinel-2
    command: >
      redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./sentinel-2.conf:/etc/redis/sentinel.conf
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
    networks:
      - redis-net

  sentinel-3:
    image: redis:7.2
    container_name: sentinel-3
    command: >
      redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./sentinel-3.conf:/etc/redis/sentinel.conf
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
    networks:
      - redis-net

networks:
  redis-net:
    driver: bridge

哨兵操作命令

# 连接 Sentinel
redis-cli -p 26379

# 查看监控的主节点
SENTINEL masters

# 查看主节点的从节点
SENTINEL replicas mymaster

# 获取当前主节点地址
SENTINEL get-master-addr-by-name mymaster
# 1) "192.168.1.100"
# 2) "6379"

# 查看 Sentinel 节点信息
SENTINEL sentinels mymaster

# 手动触发故障转移
SENTINEL failover mymaster

# 检查配置
SENTINEL ckquorum mymaster

Python 使用 Sentinel

from redis.sentinel import Sentinel

# 连接 Sentinel
sentinel = Sentinel([
    ('192.168.1.1', 26379),
    ('192.168.1.2', 26379),
    ('192.168.1.3', 26379),
], socket_timeout=0.5)

# 获取主节点连接
master = sentinel.master_for('mymaster', password='mypassword')
master.set('key', 'value')

# 获取从节点连接(读操作)
slave = sentinel.slave_for('mymaster', password='mypassword')
value = slave.get('key')

10.6 复制相关配置

# ---- 主节点配置 ----
# 写入复制缓冲区的最小 repl-offset 间隔
repl-diskless-sync yes          # 无磁盘复制(直接通过 socket 发送 RDB)
repl-diskless-sync-delay 5      # 无磁盘复制延迟(秒)
repl-ping-replica-period 10     # 主节点 ping 从节点的间隔

# 从节点写入限制
# 如果从节点落后主节点超过 N 秒的 offset,则拒绝写入
min-replicas-to-write 1         # 至少 N 个从节点在线才接受写入
min-replicas-max-lag 10         # 从节点最大延迟(秒)

# ---- 从节点配置 ----
replica-serve-stale-data yes    # 同步期间是否提供旧数据
replica-read-only yes           # 从节点只读
replica-priority 100            # 故障转移优先级(0 表示不参与)

📌 业务场景

场景一:读写分离

# 写操作走主节点
redis-master SET user:1001 '{"name":"张三"}'

# 读操作走从节点
redis-slave GET user:1001

场景二:数据备份

# 从节点关闭持久化(由主节点负责持久化)
# 从节点用于数据备份和灾难恢复

场景三:高可用方案

# 3 个 Sentinel + 1 主 + 2 从
# 自动故障转移,客户端通过 Sentinel 发现主节点

🔗 扩展阅读