前置知识: Redis

无盘复制

5 min高级

Redis无盘复制:避免磁盘I/O的复制方案、配置与适用场景

1. 无盘复制概述

无盘复制(Diskless Replication)是 Redis 2.8.18 引入的特性,允许在主从复制过程中跳过磁盘 I/O,直接通过网络将 RDB 数据从主节点发送到从节点,无需在主节点磁盘上生成临时 RDB 文件。

传统复制流程:

plaintext
主节点内存 → RDB文件(磁盘) → 读取磁盘 → 网络发送 → 从节点

无盘复制流程:

plaintext
主节点内存 → 网络直接发送 → 从节点

2. 传统复制的磁盘瓶颈

2.1 问题分析

传统主从复制的 RDB 生成过程涉及两次磁盘 I/O:

  1. 写入磁盘:子进程将 RDB 数据写入临时文件
  2. 读取磁盘:主进程读取临时文件发送给从节点

当数据集较大时,磁盘 I/O 成为瓶颈:

复制总耗时=fork 时间+数据集大小磁盘写入速度+数据集大小磁盘读取速度+数据集大小网络带宽\text{复制总耗时} = \text{fork 时间} + \frac{\text{数据集大小}}{\text{磁盘写入速度}} + \frac{\text{数据集大小}}{\text{磁盘读取速度}} + \frac{\text{数据集大小}}{\text{网络带宽}}

2.2 磁盘瓶颈场景

场景磁盘类型数据集大小磁盘写入耗时网络传输耗时
HDD + 大数据集HDD50 GB~500s~400s (1Gbps)
SSD + 大数据集SSD50 GB~50s~400s (1Gbps)
SSD + 小数据集SSD5 GB~5s~40s (1Gbps)

对于 HDD 环境,磁盘 I/O 可能成为复制的主要瓶颈。

2.3 临时文件对磁盘的影响

  • 临时 RDB 文件可能占用大量磁盘空间
  • 频繁的全量同步会加速磁盘磨损(SSD)
  • 磁盘空间不足会导致复制失败

3. 无盘复制工作原理

3.1 核心机制

无盘复制通过管道(pipe)将 RDB 数据直接从子进程传输到网络套接字:

主节点:

fork() → 子进程

生成 RDB 数据

管道(pipe)

主进程读取管道 → 网络发送 → 从节点

主节点:

fork() → 子进程

生成 RDB 数据

管道(pipe)

主进程读取管道 → 网络发送 → 从节点

mermaid diagram

3.2 详细流程

  1. 主节点收到从节点的 PSYNC 请求
  2. 主节点判断使用无盘复制
  3. 主节点 fork() 子进程
  4. 子进程将 RDB 数据写入管道(而非磁盘文件)
  5. 主进程从管道读取数据,通过套接字发送给从节点
  6. 从节点接收 RDB 数据,写入临时文件
  7. 从节点加载临时 RDB 文件完成同步

3.3 管道缓冲区

管道有容量限制,当子进程写入速度超过网络发送速度时,管道缓冲区满,子进程被阻塞:

c
// 管道缓冲区大小
#define PIPE_BUF 65536  // 64KB (Linux 默认)

// 实际 Redis 使用更大的管道
// 通过 fcntl(fd, F_SETPIPE_SZ, size) 调整

Redis 会根据网络带宽动态调整管道大小,避免子进程频繁阻塞。

4. 配置与启用

4.1 核心配置

plaintext
# 开启无盘复制
repl-diskless-sync yes

# 无盘复制延迟启动时间(秒)
repl-diskless-sync-delay 5

# 无盘复制加载模式(Redis 7.0+)
repl-diskless-load on-empty-db

4.2 repl-diskless-sync-delay

延迟启动是为了等待更多从节点同时连接,实现并行复制:

主节点从节点3从节点2从节点1主节点从节点3从节点2从节点1delay 到期连接,等待 delay 秒连接,等待 delay 秒连接,等待 delay 秒一次性发送 RDB一次性发送 RDB一次性发送 RDB
主节点从节点3从节点2从节点1主节点从节点3从节点2从节点1delay 到期连接,等待 delay 秒连接,等待 delay 秒连接,等待 delay 秒一次性发送 RDB一次性发送 RDB一次性发送 RDB
mermaid diagram
  • 设为 0:第一个从节点请求时立即开始复制
  • 设为 5(默认):等待 5 秒,收集更多从节点
  • 设为较大值:适合大量从节点同时同步的场景

4.3 repl-diskless-load

Redis 7.0+ 支持从节点无盘加载,直接从网络套接字解析 RDB 数据到内存:

plaintext
# 配置选项
repl-diskless-load disabled    # 禁用(默认,先写磁盘再加载)
repl-diskless-load on-empty-db # 仅当从节点数据库为空时使用
repl-diskless-load swapdb      # 使用交换数据库(保留旧数据直到加载完成)
repl-diskless-load flush-before-load  # 直接清空后加载(不推荐)

swapdb 模式:

plaintext
1. 从节点创建备份数据库(swapdb)
2. 直接从网络加载 RDB 到主数据库
3. 加载成功后,删除备份数据库
4. 加载失败时,从备份数据库恢复

5. 无盘复制的适用场景

5.1 适合无盘复制

场景原因
HDD 磁盘磁盘 I/O 是瓶颈,无盘复制大幅提升速度
临时磁盘避免写入临时文件
高频全量同步减少磁盘磨损
磁盘空间有限不需要额外空间存储 RDB 文件
多从节点同时同步并行发送效率更高

5.2 不适合无盘复制

场景原因
SSD 磁盘磁盘 I/O 不是瓶颈
网络带宽受限网络成为瓶颈,无盘复制无优势
单从节点无并行复制收益
需要持久化 RDB无盘复制不生成磁盘文件

5.3 决策流程

磁盘类型?

HDD → 网络带宽 > 磁盘写入速度? → 是 → 开启无盘复制

否 → 不开启

SSD → 从节点数量 > 3? → 是 → 考虑开启

否 → 不开启

磁盘类型?

HDD → 网络带宽 > 磁盘写入速度? → 是 → 开启无盘复制

否 → 不开启

SSD → 从节点数量 > 3? → 是 → 考虑开启

否 → 不开启

mermaid diagram

6. 性能对比

6.1 复制耗时对比

假设数据集 20GB,HDD 写入速度 100MB/s,网络带宽 1Gbps:

方案磁盘写入磁盘读取网络传输总耗时
传统复制200s200s160s~560s
无盘复制0s0s160s~160s
提升比例---3.5x

6.2 内存开销

无盘复制在复制期间的内存开销:

额外内存=fork 开销+管道缓冲区+COW 开销\text{额外内存} = \text{fork 开销} + \text{管道缓冲区} + \text{COW 开销}

与传统复制相比,无盘复制的内存开销基本相同,因为两者都需要 fork() 和 COW 机制。

7. 无盘复制的注意事项

7.1 网络中断

  • 无盘复制过程中网络中断,无法断点续传
  • 必须重新发起全量同步
  • 传统复制可以从已写入的 RDB 文件继续

7.2 多从节点同步

  • 无盘复制支持同时向多个从节点发送 RDB
  • 但如果从节点在不同时间请求,后到的从节点需要等待下一次全量同步
  • repl-diskless-sync-delay 可以缓解此问题

7.3 与 AOF 的交互

  • 无盘复制不影响 AOF 持久化
  • AOF 仍然正常写入磁盘
  • 无盘复制仅跳过复制过程中的 RDB 磁盘写入

7.4 监控指标

plaintext
# 查看复制状态
INFO Replication

# 关键指标
# repl_diskless_sync: 是否开启无盘复制
# repl_diskless_sync_delay: 延迟时间
# connected_slaves: 已连接从节点数