一、进程之间的通讯方式
1. 管道
特点: 简单、效率较高,但通信方向受限
1.1 概念
- 管道本质上是一个内核缓冲区(内存中的文件),它可以看作是一个 先进先出 的字节流
- 一个进程把数据写入管道,另一个进程从管道读取数据
- 管道在用户看来就像是一个文件描述符(fd),但数据只能单向流动(读或写)
1.2 特点
- 半双工通信
数据只能单向流动,如果要实现双向通信,需要建立两个管道。 - 面向字节流
和 socket 类似,不保证消息边界,只保证字节顺序(可以用消息头进行封装,类似处理粘包问题) - 一次性读取
管道内数据被读出后就消失了,不会像文件那样保留。 - 内核管理
管道是由操作系统内核维护的,用户态不可直接操作
1.3 分类
(1) 匿名管道
- 通过系统调用 pipe(int fd[2]) 创建
- fd[0]用于读,fd[1]用于写
- 一般用于父子进程通信,因为必须在fork函数之前创建
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int fd[2];
if (pipe(fd) == -1) { perror("pipe"); return 1; }
pid_t pid = fork();
if (pid == -1) { perror("fork"); return 1; }
if (pid == 0) { // 子进程:写
close(fd[0]); // 关读端(非常关键)
const char *msg = "hello from child\n";
write(fd[1], msg, strlen(msg));
close(fd[1]); // 关写端,通知父进程可能到 EOF
_exit(0);
} else { // 父进程:读
close(fd[1]); // 关写端
char buf[1024];
ssize_t n;
while ((n = read(fd[0], buf, sizeof buf)) > 0) {
fwrite(buf, 1, n, stdout);
}
close(fd[0]);
wait(NULL);
}
}
要点:
一定关闭不用的端,否则读不到 EOF、还可能死锁。
读循环要处理部分读,直到 read() 返回 0(EOF)。
(2) 命名管道
mkfifo /tmp/myfifo
# 终端A:
echo "hi" > /tmp/myfifo
# 终端B:
cat < /tmp/myfifo

要点:
FIFO 是文件系统对象,受权限/umask 管控;删除用 unlink()。
通过 mkfifo() 创建,表现为文件系统中的一个特殊文件。
任何不相关的进程,只要有访问权限,都能通过这个管道通信。
适合多个无亲缘关系进程之间通信。
1.4 管道的实现原理
- 管道对应内核中的一个缓冲区(典型大小 4KB 或更大,视系统而定)
- 读写操作通过文件描述符完成:
– 写端写入数据,数据进入内核缓冲区;
– 读端从缓冲区取出数据; - 阻塞特性:
– 如果管道满了,写操作会阻塞,直到有进程读走数据;
– 如果管道空了,读操作会阻塞,直到有进程写入数据; - 关闭规则:
– 如果所有写端都关闭了,读端会读到 EOF;
– 如果所有读端都关闭了,写入会产生 SIGPIPE 信号;
1.5 适用场景
- 父子进程通信:最经典,比如 Shell 管道命令 ls | grep txt ;
- 简单数据传递:命名管道可用于不同程序之间传递少量数据 ;
- 组合命令:Linux 中的管道符 | 就是基于管道机制实现的 ;
2. 消息队列
特点: 支持随机读写(不像管道是先进先出);不适合传输特别大数据
2.1 概念
消息队列是一种 内核维护的链表结构,进程之间可以通过它传递数据块(称为 消息 message);
每条消息带有类型(type)和数据(data),进程可以根据消息类型选择性读取
2.2 特点
- 以消息为单位
避免了因嗯用曾自己做分隔 - 支持随机接收
可以按消息类型接收,而不仅仅是先进先出 - 支持随机接收
可以按消息类型接收,而不仅仅是先进先出 - 内核维护
消息队列存在于内核,只有在系统关闭或显式删除时才释放 - 不要求进程同时存在
发送者和接收者可以异步通信(不像管道必须配对读写) - 容量限制
消息队列有最大字节数限制(Linux 默认 /proc/sys/kernel/msgmnb,单条消息大小 /proc/sys/kernel/msgmax) - 可靠性
比信号安全(不会丢消息),但容量满了写入会阻塞或返回错误
2.3 分类
(1) System V 消息队列
较早的实现方式,通过 消息队列标识符访问
常用系统调用:
msgget() —— 创建/获取消息队列
msgsnd() —— 发送消息
msgrcv() —— 接收消息
msgctl() —— 控制消息队列(删除、查看信息等)
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstring>
#include <iostream>
#include <unistd.h>
struct MsgBuf {
long mtype; // 消息类型 > 0
char mtext[128]; // 消息正文
};
int main() {
key_t key = ftok("mqfile", 65); // 生成唯一 key
int msgid = msgget(key, IPC_CREAT | 0666); // 创建消息队列
if (msgid == -1) {
perror("msgget");
return 1;
}
pid_t pid = fork();
if (pid == 0) {
// 子进程:发送消息
MsgBuf msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello from child");
if (msgsnd(msgid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
perror("msgsnd");
}
} else {
// 父进程:接收消息
MsgBuf msg;
if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
perror("msgrcv");
} else {
std::cout << "父进程收到消息: " << msg.mtext << std::endl;
}
// 删除消息队列
msgctl(msgid, IPC_RMID, nullptr);
}
return 0;
}
输出:
父进程收到消息: Hello from child
(2) POSIX 消息队列
提供更现代的接口,支持 通知机制(比如接收到消息时发送信号或唤醒线程)。
常用函数:
mq_open() / mq_close()
mq_send() / mq_receive()
mq_unlink()
#include <mqueue.h>
#include <fcntl.h> // O_CREAT, O_RDWR
#include <sys/stat.h> // 权限
#include <cstring>
#include <iostream>
#include <unistd.h>
int main() {
const char *mq_name = "/test_mq";
// 创建消息队列
mqd_t mq = mq_open(mq_name, O_CREAT | O_RDWR, 0666, nullptr);
if (mq == (mqd_t)-1) {
perror("mq_open");
return 1;
}
pid_t pid = fork();
if (pid == 0) {
// 子进程:发送消息
const char *msg = "Hello from child (POSIX MQ)";
if (mq_send(mq, msg, strlen(msg) + 1, 0) == -1) {
perror("mq_send");
}
} else {
// 父进程:接收消息
char buf[128];
if (mq_receive(mq, buf, sizeof(buf), nullptr) == -1) {
perror("mq_receive");
} else {
std::cout << "父进程收到消息: " << buf << std::endl;
}
mq_close(mq);
mq_unlink(mq_name); // 删除消息队列
}
return 0;
}
注:POSIX 消息队列名字必须以 / 开头,像文件名一样
2.4 实现原理
-
内核为每个消息队列维护一个 消息链表。
-
每个消息包含:
类型
数据
长度
下一条消息指针 -
msgsnd 把消息挂到队列尾部;
-
msgrcv 从队列头或指定类型取出消息。
2.5 应用场景
- 解耦:发送和接收进程不需要同时运行。
- 多生产者-多消费者模型。
- 日志收集:多个进程写入消息队列,一个进程异步收集处理。
- 任务调度:一个进程下发任务,多个工作进程取消息处理。
3. 共享内存
3.1 概念
把一段物理内存页同时映射到多个进程的地址空间,这样各进程可以像读写普通内存一样交换数据
3.2 特点
- 最快的本机 IPC(没有内核态到用户态的拷贝往返)。
- 无消息边界:只是内存,需要自己做同步与数据协议(头部、长度、状态位等)。
- 生命周期:由内核管理;POSIX 需要 shm_unlink() 删除命名对象;System V 用 shmctl(…, IPC_RMID, …)
3.3 分类
- POSIX 共享内存:shm_open + ftruncate + mmap + munmap + shm_unlink
名字像文件:“/my_shm”,实现在多数 Linux 下落在 /dev/shm/。
搭配 POSIX 命名信号量(sem_open/sem_wait/sem_post/sem_unlink)或 进程共享的 pthread 互斥量/条件变量 做同步。 - System V 共享内存:shmget + shmat + shmdt + shmctl
用 key_t 标识(可 ftok 生成),系统命令 ipcs/ipcrm 可查看/删除。
常搭配 System V 信号量(semget/semop/semctl)做同步。 - mmap 文件映射:mmap(…, MAP_SHARED) 把普通文件映射成共享区域(持久化/跨重启),也很常用。
核心要点:共享内存必须配合同步原语,否则就是“读脏数据/撕裂数据/竞态条件”的温床。。。
3.4 应用场景
高吞吐日志、行情/传感器共享、进程内“内存数据库”、多生产者多消费者环形队列等
4. 信号量
4.1 概念
信号量是一个整型计数器,由操作系统内核维护,用来表示某类资源的可用数量
4.2 特点
- 本质作用:通过 P()(wait,申请)和 V()(signal,释放)操作,控制多个进程/线程对共享资源的访问
- 重点:信号量本身不传输数据,只是用来做同步和互斥
4.3 分类
- 计数信号量
值可以是 0 或任意正整数。
表示可用资源数量,例如:打印机资源池有 3 台,信号量初始化为 3。 - 二进制信号量(互斥锁的一种特例)
值只能是 0 或 1。
类似互斥锁,常用于保护临界区
4.4 操作原理
- 两个基本操作:
P 操作(申请)
如果信号量 > 0,执行 信号量–,进程继续执行;
如果信号量 = 0,进程阻塞,直到信号量 > 0。
V 操作(释放)
执行 信号量++;
如果有等待进程,会唤醒其中一个。
口诀:P 操作抢资源,V 操作还资源。
4.5 实现方式
- System V 信号量
接口函数:
semget():创建 / 获取信号量集
semop():对信号量进行 P/V 操作
semctl():控制(初始化、删除)
适合进程级别同步。 - POSIX 信号量
两种形式:
命名信号量(sem_open,跨进程使用)
匿名信号量(sem_init,需放在共享内存或多线程共享变量中)
常用接口:
sem_init() / sem_destroy()
sem_wait() / sem_post()
sem_open() / sem_close() / sem_unlink()
#include <iostream>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <cstring>
struct ShmData {
char buf[128];
};
int main() {
const char* shm_name = "/demo_shm";
const char* sem_name = "/demo_sem";
// 创建共享内存
int fd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);
ftruncate(fd, sizeof(ShmData));
ShmData* data = (ShmData*)mmap(nullptr, sizeof(ShmData),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
// 创建命名信号量(初始值 0)
sem_t* sem = sem_open(sem_name, O_CREAT, 0666, 0);
pid_t pid = fork();
if (pid == 0) {
// 子进程:写数据
strcpy(data->buf, "Hello from child via shared memory!");
sem_post(sem); // 通知父进程
_exit(0);
} else {
// 父进程:等待信号量
sem_wait(sem);
std::cout << "父进程收到: " << data->buf << std::endl;
// 清理
sem_close(sem);
sem_unlink(sem_name);
munmap(data, sizeof(ShmData));
shm_unlink(shm_name);
}
return 0;
}
4.6 应用场景
- 互斥锁
二进制信号量初始化为 1,进入临界区前 sem_wait(),退出时 sem_post()。 - 生产者-消费者模型
一个信号量表示“可用数据数量”,一个信号量表示“空闲缓冲区数量”。
保证不会出现写满或读空。 - 进程/线程同步
等待另一个进程执行到某一步再继续。
4.7 注意事项
- 信号量遗留:System V 的信号量若忘记删除,会残留在内核,可用 ipcs -s / ipcrm 清理
- 死锁风险:如果 sem_wait() 后忘记 sem_post(),可能所有进程都卡死
- 优先级反转:如果高优先级进程等待低优先级进程释放信号量,可能出现性能问题
- 进程退出时处理:进程崩溃可能导致信号量状态不一致,需要健壮性机制(robust mutex 等)
- 跨平台差异:Windows 没有 POSIX 信号量,用 CreateSemaphore()
5. 信号
5.1 概念
- 信号不是数据通道,而是 一种事件通知机制。
- 操作系统或进程向某个进程发送信号时,会 打断该进程的执行流,触发一个预定义的处理动作。
- 可以类比为:
硬件中断 → 处理器通知进程;
信号 → 内核/其他进程通知进程。 - 在 IPC 体系里,信号属于“控制型通信”手段,而非“数据传输型”。
5.2 信号的传递方式
- 用户进程发送
进程 A 调用 kill(pid, sig) 向进程 B 发送信号。
如果 pid = 0,信号会发给调用进程所在进程组。
如果 pid = -pgid,信号发给对应进程组的所有进程。 - 内核发送
子进程退出时,内核向父进程发送 SIGCHLD。
管道写端关闭,读进程会收到 SIGPIPE。
定时器到期,内核给进程发送 SIGALRM。 - 线程级别
在多线程程序中,信号可以:
广播给进程内某个未屏蔽该信号的线程。
或通过 pthread_kill 定向发给某个线程。
父进程杀子进程
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
while (1) pause(); // 等待信号
} else {
sleep(2);
kill(pid, SIGTERM); // 父进程给子进程发终止信号
printf("Child killed\n");
}
}
使用 SIGCHLD 收集子进程退出状态
#include <signal.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig) {
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("Child %d exited\n", pid);
}
}
int main() {
signal(SIGCHLD, handler);
if (fork() == 0) {
_exit(0); // 子进程立即退出
}
while (1) pause();
}
5.3 常见信号
SIGCHLD 子进程退出 通知父进程调用 wait()/waitpid() 回收资源
SIGPIPE 写入已关闭管道/Socket 通知写端进程“对端已关闭”
SIGHUP 控制终端关闭 常用来让守护进程重读配置
SIGTERM kill 默认 优雅退出,通常比 SIGKILL 更常用
SIGKILL 强制 kill -9 直接杀死进程(不可处理)
5.4 应用场景
- 控制进程的生命周期(启动、暂停、终止)。
- 进程间通知事件发生(子进程退出,定时器到期等)。
- 异常上报(内存访问错误、管道断裂等)
6. 套接字
6.1 概念
套接字(Socket)是一种 通信端点(endpoint)抽象,由 IP地址 + 端口号 唯一标识,Socket 提供了一套 类文件的 API(read/write,send/recv),让不同进程之间能收发数据
6.2 分类
- 本地套接字
用于同一台机器上的进程间通信。
地址不是 IP+端口,而是 文件路径(如 /tmp/mysock)。
性能比 TCP/UDP 高(走内核缓冲区,不经过网络协议栈)。 - 网络套接字
跨主机通信,基于 IP 地址 + 端口号。
常见协议:
TCP 套接字(SOCK_STREAM):面向连接、可靠、字节流。
UDP 套接字(SOCK_DGRAM):无连接、不可靠、报文。
6.3 应用场景
- 本地进程间通信
Web 服务器(Nginx、Apache)与 FastCGI/后端程序通信常用 UNIX Domain Socket。
数据库(MySQL、PostgreSQL)本地连接时也常走 UDS。 - 跨主机通信
客户端与服务器模型(HTTP、RPC)。
分布式系统节点间同步(ZooKeeper、Redis Cluster)。 - 混合使用
本地进程间用 UDS
远程进程间用 TCP
7. 内存映射文件
7.1 概念
把一个文件的内容映射到进程的虚拟内存地址空间中。进程可以像读写内存一样读写文件
7.2 特点
- 高效
避免了用户态和内核态之间的数据拷贝(零拷贝)
直接在内存中操作文件内容 - 双向通信
多个进程共享映射区域,可以互相写入/读取 - 持久性
映射的是文件,修改内容可以持久保存到磁盘(除非映射的是匿名内存) - 灵活性
可以映射普通文件(持久通信)
也可以映射特殊文件,用于匿名共享内存(临时通信)
7.3 分类
- 文件映射
mmap 一个实际存在的文件。
通常用于多个进程共享数据,同时能持久化。 - 匿名映射
mmap 时传入 MAP_ANONYMOUS(或映射 /dev/zero)。
不依赖文件,内容存在于内存,进程结束即消失。
常用于进程间共享大块临时数据。 - 共享映射(MAP_SHARED)
所有进程看到相同的内存内容,修改会反映到文件中。 - 私有映射(MAP_PRIVATE)
“写时复制”机制(Copy-on-Write),修改只在本进程可见,不会影响文件或其他进程。
Writer进程
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main() {
int fd = open("shared.dat", O_RDWR | O_CREAT, 0666);
ftruncate(fd, 4096); // 设置文件大小
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
strcpy((char *)addr, "Hello from writer!");
munmap(addr, 4096);
close(fd);
return 0;
}
Reader 进程
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("shared.dat", O_RDWR, 0666);
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf("Reader read: %s\n", (char *)addr);
munmap(addr, 4096);
close(fd);
return 0;
}
7.4 应用场景
- 大规模数据共享:数据库、日志系统
- 文件读写优化:比 read/write 更快(内核直接分页)
- 进程间共享内存
- 内存映射 I/O:驱动程序与设备通信
二、虚拟内存
1. 概念
虚拟内存 是操作系统提供的一种 内存管理机制,它让每个进程看起来都拥有一块 连续且独立的内存空间,而不必受限于物理内存的实际大小和分布
- 进程看到的内存地址(虚拟地址) ≠ 真实的物理内存地址
- 操作系统和硬件(CPU 的 MMU - 内存管理单元)会把 虚拟地址 翻译成 物理地址
2. 特点
- 提供 逻辑上连续、独立的地址空间
- 通过 分页机制 实现地址映射
- 依赖 页表 + TLB + 缺页中断 来运作
- 作用是 安全、扩展、方便、高效共享
3. 作用
3.1 地址空间隔离
每个进程都有独立的虚拟地址空间
一个进程不能直接访问另一个进程的内存,保证安全性和稳定性
3.2 内存扩展(超出物理内存)
可以使用硬盘的一部分作为“交换空间”(swap 或 pagefile),使得程序能使用比物理内存更大的地址空间
3.3 简化编程
程序不用考虑物理内存的具体分配,只需要操作虚拟地址
3.4 内存保护
操作系统可以控制某段内存是否可读、可写、可执行
3.5 高效共享
多个进程可以共享同一段物理内存(例如共享库、只读数据),但在虚拟地址空间中看起来还是独立的
4. 实现机制
-
虚拟内存主要通过 分页(Paging) 实现:
分页:虚拟内存和物理内存都划分为固定大小的块(通常 4KB,称为 页(Page))
页表:记录虚拟页到物理页的映射关系
MMU(内存管理单元):CPU 硬件负责在访问内存时,根据页表将虚拟地址 → 物理地址 -
页表查找过程:
程序访问虚拟地址
MMU 根据页表找到对应的物理地址
如果页不在物理内存里(缺页中断),操作系统会:
从磁盘把需要的数据调入内存(换页)
可能需要把某些页换出到磁盘 -
快速查找优化:
TLB(快表):存储最近的虚拟地址→物理地址映射,提高速度
三、页表
1. 概念
- 页 (Page):虚拟内存被划分成大小固定的块,比如 4KB
- 页框 (Page Frame):物理内存对应的块。大小也相同,比如 4KB
- 页表中的每一项(Page Table Entry, PTE)描述了某个虚拟页对应的物理页框
2. 作用
- 在操作系统的内存管理中,使用 虚拟内存 技术。
- 程序访问的地址是 虚拟地址(Virtual Address,VA)。
- 真正的物理内存用的是 物理地址(Physical Address,PA)。
- 页表 就是虚拟地址和物理地址之间的“映射关系表”。
换句话说:
当 CPU 访问虚拟地址时,操作系统通过 页表 找到对应的物理地址。
3. 页表项 (PTE) 的内容
- 页框号 (PFN):映射到哪个物理页框
- 有效位 :这个页是否在内存里(不在就触发缺页异常)
- 读/写/执行权限 (R/W/X bits):控制内存访问权限
- 访问位:是否被访问过(用于页面置换算法)
- 脏位:是否被修改过(写回磁盘时要用)
4. 多级页表
如果用一个 单级页表:
32位地址空间,4KB页大小
需要的页数:
232 / 212 = 220 = 1M
每个页表项假设 4B,总共需要 4MB 页表
但对于 64 位地址空间,这会更巨大,所以一般使用 多级页表;
4.1 思路
把页表分层,逐级缩小范围
- 不必一次性存下所有页表
- 只有真正被使用的部分才建立页表
4.2 32位二级页表示例
虚拟地址 32 位,页大小 4KB → 页内偏移 12 位
剩下 20 位做虚拟页号,可以拆成:
- 10 位:一级页表索引
- 10 位:二级页表索引
- 12 位:页内偏移
流程:
- CPU 取出前 10 位 → 找一级页表,得到某个二级页表的基地址
- 再取中间 10 位 → 找二级页表,得到物理页框号
- 拼上后 12 位偏移 → 得到物理地址
每一级页表都不必完整存在,减少内存浪费。
4.3 64位系统
x86-64 常用 四级页表(甚至五级页表)。
虚拟地址拆分:
- 9 位 → PML4 (最高级页表)
- 9 位 → PDPT (Page Directory Pointer Table)
- 9 位 → Page Directory
- 9 位 → Page Table
- 12 位 → 页内偏移
合计:48 位可用虚拟地址。
5. 硬件加速:TLB
查多级页表很慢(可能要访问 4~5 次内存)。
→ CPU 提供 TLB (Translation Lookaside Buffer)。
- TLB = 地址映射的缓存
- 如果 TLB 命中 → 直接得到物理地址,几乎无延迟
- 如果未命中 → 再去查页表(慢)
所以 TLB 命中率对性能非常关键。
6. 工作流程示例
程序要访问虚拟地址 VA:
- CPU 拿出 VA 的高几位 → 找页表 → 得到物理页框号。
- VA 的低几位作为 页内偏移,拼接上物理页框号 → 得到物理地址。
- 若页表项无效 → 触发 缺页异常,操作系统从磁盘调入该页。
7. 缺页异常与页面置换
如果某个虚拟页对应的 PTE 的 Present = 0,说明数据不在内存,而在磁盘。
CPU 访问时会:
- 触发 缺页异常
- 操作系统把需要的页从磁盘读入内存
- 更新页表
- 重新执行指令
8. 总结
- 页表是虚拟地址 → 物理地址的翻译器
- 页表项不仅存放物理页框号,还有权限和状态信息
- 多级页表解决了页表太大的问题
- TLB提升了地址翻译速度
- 缺页异常 + 页面置换使虚拟内存扩展到磁盘
四、Redis持久化
1. RDB 持久化(快照方式)
原理:在指定的时间间隔内,将某一时刻的内存数据快照保存到磁盘上的一个 .rdb 文件。
触发方式:
-
自动触发:
在配置文件 redis.conf 中通过 save 指令设置(例如 save 900 1 表示 900 秒内至少 1 次修改就触发快照)。 -
手动触发:
SAVE:在主线程中直接生成 RDB 文件(阻塞 Redis)。
BGSAVE:fork 子进程去生成 RDB 文件(推荐使用,非阻塞)。
优点:
- 文件体积小,适合备份和灾难恢复。
- 恢复速度快。
缺点:
- 数据不是实时持久化的,可能丢失最后一次快照后的数据。
- BGSAVE 需要 fork 子进程,数据量大时比较耗时。
2. AOF 持久化(追加日志方式)
原理:
- 以日志形式记录服务器接收到的每一个写命令,并将其写入到 .aof 文件中。
触发机制:
- 通过 appendfsync 参数控制刷盘策略:
- always:每次写命令都刷盘(最安全,性能最差)。
- everysec:每秒刷一次盘(折中方案,最多丢 1 秒数据)。
- no:依赖操作系统决定何时刷盘(性能最好,安全性最差)。
重写机制:
- 随着时间推移,AOF 文件会越来越大。
- Redis 会定期触发 AOF 重写(BGREWRITEAOF),将当前内存中的数据转换为最简化的写命令序列,生成新的 AOF 文件。
优点:
- 数据更安全,通常只会丢失 1 秒的数据(取决于配置)。
- 可读性好,AOF 文件能直接理解为一系列 Redis 命令。
缺点:
- 文件体积比 RDB 大。
- 恢复速度比 RDB 慢。
3. 混合持久化(RDB + AOF)
从 Redis 4.0 开始支持。
原理:
- 在进行 AOF 重写时,先把当前内存快照以 RDB 形式写入 AOF 文件,再追加后续的写命令。
优点:
- 结合了 RDB 和 AOF 的优点:既能保证恢复速度快(靠 RDB),又能保证数据不丢失(靠 AOF)。
缺点:
- 配置和理解比单一模式复杂。
4. 持久化的选择
- 只要数据不重要,可以不用持久化(缓存场景)。
- 对数据可靠性要求高 → 推荐 AOF everysec。
- 数据重要但恢复速度也要快 → 推荐 混合持久化。
- 数据量特别大,主要靠备份恢复 → 推荐 RDB。
869

被折叠的 条评论
为什么被折叠?



