Redis bgsave 为什么不阻塞
关联笔记: Redis与缓存考点总结、RDB、AOF
核心答案
bgsave 使用 fork() 创建子进程来执行持久化,父进程(主线程)继续处理客户端请求,因此不阻塞。
save vs bgsave 对比
save(阻塞):
主线程 → [执行save] → [写磁盘完成] → 继续处理请求
↑______________↑
这段时间 Redis 完全卡死
bgsave(不阻塞):
主线程 → fork() → 继续处理请求 ──────────────────→
↑
子进程 → [写磁盘完成] → 通知父进程
| 对比项 | save | bgsave |
|---|---|---|
| 执行线程 | 主线程 | 子进程 |
| 是否阻塞 | 阻塞 | 不阻塞 |
| 适用场景 | 不推荐 | 生产环境推荐 |
bgsave 不阻塞的原理
机制:fork() + Copy-on-Write(写时复制)
第一步:fork() 创建子进程
- Redis 调用
fork()系统调用 - 操作系统创建一个子进程
- 子进程拥有父进程内存空间的逻辑副本
第二步:Copy-on-Write(写时复制)
关键点:fork 时子进程不会立即复制全部内存
fork 后的内存布局:
父进程(主线程) 子进程
┌──────────┐ ┌──────────┐
│ 内存页 A │──共享──→ │ 内存页 A │
│ 内存页 B │──共享──→ │ 内存页 B │
│ 内存页 C │──共享──→ │ 内存页 C │
└──────────┘ └──────────┘
↓
当父进程修改内存页 B 时:
↓
┌──────────┐ ┌──────────┐
│ 内存页 A │──共享──→ │ 内存页 A │
│ 内存页 B' │ 复制 │ 内存页 B │ ← 子进程仍读旧数据
│ 内存页 C │──共享──→ │ 内存页 C │
└──────────┘ └──────────┘
- 只有当某个进程要修改某内存页时,OS 才会复制该页
- 子进程读取的是 fork 那一刻的数据快照
- 父进程继续正常服务,修改数据不影响子进程
第三步:各司其职
| 进程 | 职责 | 是否阻塞 |
|---|---|---|
| 父进程(主线程) | 继续处理客户端请求 | 不阻塞 |
| 子进程 | 将内存数据写入 RDB 文件 | 不影响父进程 |
潜在的短暂阻塞点
fork() 调用本身会有一个极短暂的阻塞(微秒到毫秒级),用于:
- 复制页表
- 创建子进程控制块
例外情况: 如果 Redis 实例内存非常大(几十GB),fork 的时间会变长,可能出现短暂卡顿。
考试答题模板
问:为什么 bgsave 不阻塞?
答:
- bgsave 使用
fork()创建子进程来执行持久化 - 子进程通过 Copy-on-Write(写时复制) 机制读取 fork 时刻的数据快照
- 父进程(主线程)继续处理客户端请求,不受影响
- 子进程完成写入后通知父进程