对于ChatRoom答辩时问题的解答

一、进程之间的通讯方式

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 实现方式

  1. System V 信号量
    接口函数:
    semget():创建 / 获取信号量集
    semop():对信号量进行 P/V 操作
    semctl():控制(初始化、删除)
    适合进程级别同步。
  2. 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。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值