Redis持久化-B版
Redis持久化-B版
43. Redis 持久化有哪几种方式?
回答
主要有两种:RDB 快照和 AOF 日志。RDB 是周期性持久化快照,AOF 是按写操作记录日志,支持重放恢复。
分析
Redis 的持久化机制设计得非常巧妙,它提供了两种互补的持久化方式:RDB 和 AOF。这两种方式各有特点,能够满足不同场景的需求。
RDB(Redis Database)采用快照的方式,定期将内存中的数据以二进制格式保存到磁盘。这种方式的特点是文件体积小、恢复速度快,特别适合做冷备份和容灾恢复。但它的缺点是在两次快照之间如果发生故障,可能会丢失部分数据。
AOF(Append Only File)则采用日志的方式,记录每个写操作命令。这种方式的特点是数据安全性高,即使发生故障,最多也只会丢失 1 秒内的数据(取决于 fsync 策略)。但它的缺点是文件体积大、恢复速度慢。
在实际应用中,我通常会同时开启 RDB + AOF,这样既能保证数据完整性,又能兼顾恢复效率。RDB 作为主备份,AOF 作为增量日志,两者配合使用,既保证了数据安全,又不会对性能造成太大影响。
让我们看看 Redis 中持久化相关的核心配置:
// server.h
struct redisServer {
// RDB 相关配置
char *rdb_filename; // RDB 文件名
int rdb_save_time; // 上次保存时间
int rdb_bgsave_scheduled; // 是否计划执行 bgsave
// AOF 相关配置
int aof_state; // AOF 状态
int aof_fsync; // fsync 策略
char *aof_filename; // AOF 文件名
int aof_rewrite_scheduled; // 是否计划执行 rewrite
};44. RDB 的优点是什么?
回答
体积小、恢复快、对性能影响小。特别适合冷备份和容灾恢复场景。
分析
RDB 的设计理念非常优雅,它通过二进制快照的方式,将内存中的数据以最紧凑的格式保存到磁盘。这种设计带来了几个显著的优势:
首先,RDB 文件是经过压缩的二进制格式,相比 AOF 的文本格式,文件体积要小得多。这不仅节省了磁盘空间,也减少了网络传输的开销,特别适合做数据备份和迁移。
其次,RDB 的恢复速度非常快。因为它是直接将文件读入内存,不需要像 AOF 那样重放命令,所以恢复时间与数据量成正比,而不是与命令数成正比。这对于大型数据库的恢复特别有利。
最重要的是,RDB 的生成过程是异步的。它通过 fork 子进程来完成快照的生成,主进程可以继续处理客户端请求,几乎不会阻塞 Redis 的主流程。这种设计让 RDB 特别适合在生产环境中使用。
让我们看看 RDB 保存的核心实现:
// rdb.c
int rdbSave(char *filename) {
// 创建临时文件
snprintf(tmpfile,256,"temp-%d.rdb", (int)getpid());
fp = fopen(tmpfile,"w");
// 写入 RDB 版本号
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
rioWrite(&rdb,magic,9);
// 遍历数据库
for (j = 0; j < server.dbnum; j++) {
// 写入数据库选择器
if (rdbSaveType(&rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
if (rdbSaveLen(&rdb,j) == -1) goto werr;
// 保存键值对
while((de = dictNext(di)) != NULL) {
sds keystr = dictGetKey(de);
robj key, *o = dictGetVal(de);
initStaticStringObject(key,keystr);
if (rdbSaveKeyValuePair(&rdb,&key,o,expire) == -1) goto werr;
}
}
// 写入结束标记
if (rdbSaveType(&rdb,RDB_OPCODE_EOF) == -1) goto werr;
// 重命名临时文件
if (rename(tmpfile,filename) == -1) {
// 错误处理
}
return C_OK;
}这段代码展示了 RDB 文件生成的核心流程,包括文件创建、数据写入和文件重命名等步骤。整个过程是原子的,确保数据一致性。特别是使用临时文件的方式,避免了在写入过程中发生故障导致文件损坏的问题。
45. RDB 的缺点是什么?
回答
RDB 是周期性持久化,可能会丢失最近几分钟的数据;生成过程需要 fork,内存大时对性能有影响。
分析
RDB 虽然有很多优点,但它也存在一些不可忽视的缺点。这些缺点主要来自其设计理念和实现机制:
最明显的问题是数据实时性。由于 RDB 是周期性触发的,在两次快照之间如果发生故障,可能会丢失最近几分钟的数据。这对于要求数据实时性的业务场景来说是不可接受的。比如在金融交易系统中,即使丢失几秒钟的数据也可能造成严重后果。
另一个重要问题是性能开销。RDB 的生成过程需要 fork 子进程,在 Linux 系统中,fork 操作会触发 Copy-on-Write 机制。当数据量很大时,这个操作会导致明显的内存压力,甚至可能引起 Redis 的短暂卡顿。特别是在大内存机器上,fork 操作可能会占用大量 CPU 资源,影响 Redis 的响应时间。
让我们看看 fork 相关的实现:
// server.c
int rdbSaveBackground(char *filename) {
pid_t childpid;
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1)
return C_ERR;
// 记录 fork 前的内存使用
server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid;
if (childpid == 0) {
// 子进程
closeListeningSockets(0);
redisSetProcTitle("redis-rdb-bgsave");
retval = rdbSave(filename);
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
// 父进程
server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid;
return C_OK;
}
}这段代码展示了 RDB 后台保存的实现,通过 fork 创建子进程来执行实际的保存操作。在大内存场景下,fork 操作可能导致明显的性能抖动。特别是在 Redis 4.0 之前,fork 操作会复制整个内存空间,这可能导致 Redis 服务暂时不可用。
46. AOF 的优点是什么?
回答
AOF 日志记录每次写操作,数据持久性强。支持重放机制,还可以通过 rewrite 实现文件压缩,利于恢复。
分析
AOF 的设计理念更接近传统数据库的 WAL(Write-Ahead Logging)机制,它通过记录每个写操作命令来实现数据持久化。这种设计带来了几个显著的优势:
首先,AOF 提供了更好的数据安全性。由于每个写操作都会被记录,即使发生故障,最多也只会丢失 1 秒内的数据(取决于 fsync 策略)。这种特性使得 AOF 特别适合对数据安全性要求高的场景,比如金融交易系统。
其次,AOF 文件是文本格式的,记录了 Redis 命令,这使得它具有良好的可读性。在出现问题时,可以直接查看 AOF 文件来定位问题,这对于问题排查非常有帮助。同时,AOF 文件也可以用于数据恢复和迁移。
最重要的是,AOF 提供了重写机制。当 AOF 文件过大时,可以通过重写来压缩文件大小。重写过程会分析当前数据库状态,生成能够重建当前数据的最小命令集。这个特性解决了 AOF 文件可能无限增长的问题。
让我们看看 AOF 写入的核心实现:
// aof.c
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
// 选择数据库
if (dictid != server.aof_selected_db) {
char seldb[64];
snprintf(seldb,sizeof(seldb),"%d",dictid);
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
(unsigned long)strlen(seldb),seldb);
server.aof_selected_db = dictid;
}
// 将命令转换为 RESP 格式
buf = catAppendOnlyGenericCommand(buf,argc,argv);
// 追加到 AOF 缓冲区
if (server.aof_state == AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
// 根据配置决定是否立即同步
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
aof_fsync(server.aof_fd);
}这段代码展示了 AOF 命令追加的核心流程,包括数据库选择、命令格式化和缓冲区写入等步骤。AOF 的写入是追加式的,这保证了写入性能,同时通过缓冲区机制,可以在性能和安全性之间取得平衡。
47. AOF 的缺点是什么?
回答
写入频率高,日志文件大,重写有开销,恢复速度比 RDB 慢。
分析
AOF 虽然提供了更好的数据安全性,但它也带来了一些不可忽视的问题。这些问题主要来自其实现机制和设计理念:
最明显的问题是文件体积。由于 AOF 需要记录每个写操作,在写入频繁的场景下,AOF 文件会快速增长。即使有重写机制,在重写之前,文件大小可能会达到原始数据大小的数倍。这不仅占用大量磁盘空间,也会影响文件系统的性能。
另一个重要问题是性能开销。AOF 的写入是追加式的,虽然写入性能好,但频繁的 fsync 操作会影响 Redis 的响应时间。特别是在 always 模式下,每个写操作都需要等待 fsync 完成,这会导致明显的性能下降。
重写过程也是一个性能瓶颈。AOF 重写需要扫描整个数据库,生成新的 AOF 文件,这个过程会消耗大量 CPU 和 IO 资源。虽然重写是在后台进行的,但它仍然会影响 Redis 的整体性能。
让我们看看 AOF 重写的核心实现:
// aof.c
int rewriteAppendOnlyFile(char *filename) {
rio aof;
FILE *fp;
char tmpfile[256];
// 创建临时文件
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int)getpid());
fp = fopen(tmpfile,"w");
// 初始化 RIO 对象
rioInitWithFile(&aof,fp);
// 遍历数据库
for (j = 0; j < server.dbnum; j++) {
// 写入数据库选择器
if (rioWriteBulkCount(&aof,'*',2) == 0) goto werr;
if (rioWriteBulkString(&aof,"SELECT",6) == 0) goto werr;
if (rioWriteBulkLongLong(&aof,j) == 0) goto werr;
// 遍历键值对
while((de = dictNext(di)) != NULL) {
sds keystr = dictGetKey(de);
robj key, *o = dictGetVal(de);
initStaticStringObject(key,keystr);
// 写入过期时间
if (expiretime != -1) {
if (rioWriteBulkCount(&aof,'*',3) == 0) goto werr;
if (rioWriteBulkString(&aof,"PEXPIREAT",9) == 0) goto werr;
if (rioWriteBulkString(&aof,keystr,sdslen(keystr)) == 0) goto werr;
if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr;
}
// 写入键值对
if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
if (rioWriteBulkObject(&aof,o) == 0) goto werr;
}
}
// 重命名临时文件
if (rename(tmpfile,filename) == -1) {
// 错误处理
}
return C_OK;
}这段代码展示了 AOF 重写的核心流程,包括文件创建、数据遍历和命令重写等步骤。整个过程是原子的,确保数据一致性。但这个过程会消耗大量资源,特别是在数据量大的情况下。
48. AOF 重写机制是怎么实现的?
回答
通过 fork 子进程,将当前内存数据转换成最简 Redis 写命令序列写入新文件,同时主线程继续写原日志,最后合并。
分析
AOF 重写机制是 Redis 持久化设计中的一个亮点,它巧妙地解决了 AOF 文件可能无限增长的问题。这个机制的设计非常精妙,既保证了数据安全,又不会影响 Redis 的正常服务。
重写过程的核心是 fork 子进程。主进程 fork 出一个子进程,子进程会获得主进程的内存快照。这个快照是只读的,不会受到主进程后续写操作的影响。子进程基于这个快照,生成能够重建当前数据的最小命令集,写入新的 AOF 文件。
同时,主进程会继续接收写请求,并将这些请求追加到 rewrite 缓冲区中。当子进程完成重写后,主进程会将 rewrite 缓冲区中的命令追加到新 AOF 文件中,最后用新文件替换旧文件。这个过程是原子的,确保了数据的一致性。
让我们看看 AOF 重写的完整流程:
// aof.c
int rewriteAppendOnlyFileBackground(void) {
pid_t childpid;
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1)
return C_ERR;
// 创建管道用于父子进程通信
if (pipe(server.aof_pipe_write_data_to_child) == -1) {
return C_ERR;
}
// fork 子进程
if ((childpid = fork()) == 0) {
// 子进程
closeListeningSockets(0);
redisSetProcTitle("redis-aof-rewrite");
// 执行重写
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
exitFromChild(0);
} else {
exitFromChild(1);
}
} else {
// 父进程
server.aof_child_pid = childpid;
return C_OK;
}
}这段代码展示了 AOF 重写的完整流程,包括进程创建、管道通信和文件重写等步骤。整个过程设计得非常巧妙,既保证了数据安全,又不会影响 Redis 的正常服务。
49. AOF rewrite 期间是否会丢失数据?
回答
不会。主线程会将 rewrite 期间的写操作缓存在 AOF 缓冲区,最后与子进程文件合并,保证数据不丢失。
分析
这是很多人担心的问题,其实 Redis 考虑得很周全。重写期间,子进程是静态生成新文件,主线程仍然接收写请求,并把这些新请求追加到专门的 rewrite 缓冲区中。当子进程完成后,Redis 会把 rewrite 缓冲区中的命令追加到新 AOF 文件中,最后替换旧文件。
这个机制的设计非常精妙。首先,子进程基于 fork 时的内存快照生成新的 AOF 文件,这个快照包含了 fork 时的所有数据。然后,主进程将 fork 之后的所有写操作都记录在 rewrite 缓冲区中。最后,当子进程完成重写后,主进程会将 rewrite 缓冲区中的命令追加到新 AOF 文件中。
让我们看看 rewrite 缓冲区的处理:
// aof.c
void aofRewriteBufferWrite(int fd) {
listNode *ln;
listIter li;
sds aof_buf;
// 遍历缓冲区
listRewind(server.aof_rewrite_buf_blocks,&li);
while((ln = listNext(&li))) {
aof_buf = ln->value;
// 写入文件
if (write(fd,aof_buf,sdslen(aof_buf)) == -1) {
// 错误处理
}
}
}这段代码展示了 rewrite 缓冲区的写入过程,确保重写期间的数据不会丢失。整个过程是原子的,确保了数据的一致性。这种设计让 AOF 重写既保证了数据安全,又不会影响 Redis 的正常服务。
50. RDB和AOF本质区别是什么?
回答
RDB和AOF的本质区别在于持久化方式的不同。RDB采用快照方式进行持久化,而AOF则是通过追加日志文件的方式实现持久化。这种本质区别导致了它们在文件类型、恢复速度、安全性等方面存在差异。
分析
RDB采用快照机制,将某一时刻的内存数据以二进制文件的形式保存到磁盘。这种方式生成的是二进制文件,恢复速度快,但在缓存宕机时容易丢失较多数据。由于每次保存都是一次全量操作,开销较大,通常设置至少5分钟保存一次数据。
AOF采用日志追加机制,将写命令以文本形式追加到日志文件中。这种方式生成的是文本文件,恢复速度相对较慢,但安全性更高。根据配置的刷盘策略(如everysec),最多只会丢失一秒的数据。AOF的刷盘操作相对轻量,通常设置每秒进行一次刷盘。
51. 如果RDB和AOF只能选一种,你选哪个?
回答
从业务需求角度来看,如果能够接受分钟级别的数据丢失,可以选择RDB;如果需要尽量保证数据安全,可以考虑混合持久化;如果只用AOF,建议选择everysec策略进行刷盘,在可靠性和性能之间取得平衡。
从持久化理念来看,始终开启快照是一个推荐的方式,这也是Redis官方默认开启RDB而不开启AOF的原因。同时,官方文档也明确不推荐只开启AOF。
分析
RDB的优势在于其简单性和性能。它生成的是紧凑的二进制文件,恢复速度快,适合大规模数据恢复。同时,RDB文件对Redis的性能影响较小,因为它是异步执行的。
AOF的优势在于其数据安全性。它记录了所有的写操作,可以保证数据不丢失。但是,AOF文件通常比RDB文件大,恢复速度较慢,且对Redis的性能影响较大。
混合持久化结合了两种方式的优点,既保证了数据安全性,又提供了较快的恢复速度。在Redis 4.0之后,可以通过配置开启混合持久化。
52. 介绍一下AOF的三种写回策略?
回答
AOF提供了三种写回策略:Always、Everysec和No。这三种策略在可靠性上是从高到低,而在性能上从低到高。
Always策略是每次写操作命令执行完后,同步将AOF日志数据写回硬盘;Everysec策略是每次写操作命令执行完后,先将命令写入到AOF文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;No策略是不控制写回硬盘的时机,每次写操作命令执行完后,先将命令写入到AOF文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
分析
Always策略提供了最高的数据安全性,但性能最差。它在每个事件循环都会将aof_buf缓冲区里的所有内容通过write写入AOF文件描述符,并且调用fsync同步它。这种方式在主线程中进行同步刷盘,会阻塞主线程,影响Redis的性能。
Everysec策略在性能和安全性之间取得了平衡。服务器在每个事件循环都要将aof_buf缓冲区的所有内容通过write写入到AOF文件描述符,并且每隔超过一秒就要在子线程中对AOF文件进行一次fsync同步。这种方式在后台线程进行fsync,主线程只进行write,性能较好,且最多只会丢失一秒钟的数据。
No策略提供了最好的性能,但数据安全性最低。它不会进行任何主动的fsync操作,完全依赖操作系统来决定何时将数据写入磁盘。在Linux系统中,通常每30秒会进行一次刷盘,但这个时间取决于内核的具体配置。
53. 为什么先执行Redis命令,再把数据写入AOF日志呢?
回答
先执行Redis命令,再把数据写入AOF日志主要有两个好处。首先,这可以保证写入AOF日志的命令都是正确可执行的,避免将语法错误的命令记录到日志中。其次,这种方式不会阻塞当前的写操作,因为只有在写操作命令执行成功后才会将命令记录到AOF日志。
分析
保证正确写入。如果当前的命令语法有问题,错误的命令记录到AOF日志里后可能还会进行语法检查。先执行Redis命令,再把数据写入AOF日志可以保证写入的都是正确可执行的命令。
不阻塞当前写操作。因为当写操作命令执行成功后才会将命令记录到AOF日志,避免写入阻塞。这种方式可以提高Redis的响应性能,特别是在高并发场景下。
潜在问题。这种机制也存在一些缺陷。由于执行写操作命令和记录日志是两个过程,如果Redis还没来得及将命令写入到硬盘时发生宕机,数据会有丢失的风险。同时,AOF日志的写入操作也是在主线程中执行,当Redis把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。
54. AOF子进程的内存数据跟主进程的内存数据不一致怎么办?
回答
Redis设置了一个AOF重写缓冲区,这个缓冲区在创建bgrewriteaof子进程之后开始使用。在重写AOF期间,当Redis执行完一个写命令之后,它会同时将这个写命令写入到AOF缓冲区和AOF重写缓冲区。当子进程完成AOF重写工作后,会向主进程发送一条信号。主进程收到该信号后,会调用一个信号处理函数,将AOF重写缓冲区中的所有内容追加到新的AOF文件中,使得新旧两个AOF文件所保存的数据库状态一致。
分析
AOF重写过程。Redis的AOF重写是由后台子进程bgrewriteaof来完成的,这有两个好处:子进程进行AOF重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程;子进程带有主进程的数据副本,使用子进程而不是线程,因为如果是使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。
数据一致性保证。创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,会发生写时复制,于是父子进程就有了独立的数据副本,不用加锁来保证数据安全。AOF子进程产生的时刻,数据和主进程是一致的,重写过程中主进程数据增加了,通过AOF重写缓冲区来保持最后结果一致。
55. RDB在执行快照的时候,数据能修改吗?
回答
可以。执行bgsave过程中,Redis依然可以继续处理操作命令的,数据是能被修改的,采用的是写时复制技术(Copy-On-Write, COW)。执行bgsave命令的时候,会通过fork()创建子进程,此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个,由于共享父进程的所有数据,可以直接读取主线程里的内存数据,并将数据写入到RDB文件。
分析
写时复制机制。当主线程要修改共享数据里的某一块数据,就会发生写时复制,数据的物理内存就会被复制一份,主线程在这个数据副本进行修改操作。与此同时,子进程可以继续把原来的数据写入到RDB文件。这种机制保证了数据的一致性,同时也避免了阻塞主线程。
性能影响。虽然写时复制机制允许在RDB快照过程中修改数据,但如果在这个期间有大量的写入操作,会导致主进程多拷贝一份数据,消耗大量额外的内存。因此,在RDB快照过程中,应该尽量避免大量的写入操作。
56. Redis用RDB持久化时对过期键会如何处理的?
回答
RDB分为生成阶段和加载阶段,生成阶段会对key进行过期检查,过期的key不会保存到RDB文件中;加载阶段在载入RDB文件时,Redis会正常加载所有key及其TTL,而过期Key的删除,是由专门的数据清理机制来保证,和RDB无关。
分析
生成阶段处理。在Redis使用RDB持久化时,过期键的处理方式取决于RDB生成和恢复的阶段。Redis在生成RDB快照时,不会直接删除过期的键,而是检查每个key的TTL,如果某个key已经过期,则不会写入RDB文件;如果key未过期,则会连同其TTL一起写入RDB。这样,生成的RDB文件中不会包含已过期的键,避免存储无用数据。
加载阶段处理。当Redis重启并加载RDB文件时,Redis会正常加载所有key及其TTL,但不会立即清理所有过期key,而是由数据清理机制来保证。在客户端访问key时,Redis发现key已过期,则立即删除。Redis运行时的定期清理机制可能也会被触发,主动删除过期key。
57. Redis用AOF持久化时对过期键会如何处理的?
回答
恢复时会恢复所有过期key,等待惰性删除或定期清理,写入时会记录EXPIRE命令,当此过期键被删除后,Redis会向AOF文件追加一条DEL命令来显式地删除该键值。重写阶段会对Redis中的键值对进行检查,已过期的键不会被保存到重写后的AOF文件中。
分析
AOF追加日志。在AOF持久化模式下,Redis会记录EXPIRE命令,过期后不自动删除,只有触发惰性删除或定期清理时才写入DEL命令。这种方式保证了AOF文件记录了完整的键生命周期。
AOF重写。在AOF重写阶段,Redis会对键值对进行检查,过滤掉已过期的key,生成更精简的AOF文件。这样可以减少AOF文件的大小,提高恢复效率。
AOF恢复。在加载AOF时,Redis会恢复所有key,包括过期的key。这些过期的key需要等待惰性删除或定期清理机制来处理。这种机制确保了数据的一致性,同时也避免了在恢复过程中丢失数据。
58. AOF模式下,Redis主从模式中,对过期键会如何处理?
回答
从库不会进行过期扫描,从库的过期键处理依靠主服务器控制,主库在key到期时,会在AOF文件里增加一条del指令,同步到所有的从库,从库通过执行这条del指令来删除过期的key。如果主从同步发生意外,原本主库的key过期了,但是del指令没有同步给从库成功,导致从库内存中存在已经过期但没有删除的key,这时候有客户端访问从库时,即使key还是内存的,但是从库发现key是过期的,就不会返回key的数据给客户端了。
分析
主库处理。在主从模式下,主库负责处理过期键的删除操作。当key到期时,主库会在AOF文件里增加一条del指令,并将这条指令同步到所有的从库。这种方式确保了从库能够及时删除过期的key。
从库处理。从库不会主动进行过期扫描,而是依赖主库的del指令来删除过期的key。如果主从同步发生意外,从库可能会保留过期的key,但在客户端访问时,从库会检查key是否过期,如果过期则不会返回数据。
数据一致性。这种机制保证了主从库之间的数据一致性,同时也避免了从库进行不必要的过期扫描,提高了性能。但是,如果主从同步出现问题,可能会导致从库保留过期的key,影响数据的一致性。
59. RDB持久化的触发时机?
回答
主要有这么几个地方,一个是调用save或者bgsave命令,一个是根据我们配置周期进行,一个是Redis关闭之前,这三个是比较常见的,其它边缘一点的还有主从全量复制发送RDB文件等。
分析
命令触发。RDB持久化可以通过save或bgsave命令手动触发。save命令会阻塞主线程,直到RDB文件创建完毕;bgsave命令会创建一个子进程来执行RDB持久化,不会阻塞主线程。
配置触发。Redis会根据配置文件中的save配置项自动触发RDB持久化。例如,如果配置了"save 900 1",表示在900秒内有1个修改,就会触发RDB持久化。
关闭触发。当Redis关闭时,会自动执行一次RDB持久化,确保数据不会丢失。
其他触发。在主从复制过程中,主节点会生成RDB文件发送给从节点,这时也会触发RDB持久化。此外,当执行FLUSHALL命令清空数据库时,也会触发RDB持久化。
60. AOF刷盘的触发时机?
回答
AOF触发流程主要有3个,一个是Redis关闭的时候,另一个是每一次事件循环钩子函数beforeSleep(),最后一个是每一次事件循环函数servercron里面。
分析
Redis关闭。当Redis关闭时,会执行一次AOF刷盘操作,确保数据不会丢失。
事件循环钩子。在每一次事件循环的beforeSleep()钩子函数中,Redis会根据配置的appendfsync策略决定是否进行AOF刷盘。always策略会在每个事件循环都进行刷盘,everysec策略会每秒进行一次刷盘,no策略则不会主动进行刷盘。
时间事件。在每一次事件循环的servercron()函数中,Redis会检查是否需要执行AOF刷盘。这个函数会定期执行,确保AOF文件能够及时写入磁盘。
刷盘策略。根据配置的appendfsync策略,AOF刷盘的行为会有所不同。always策略提供了最高的数据安全性,但性能最差;everysec策略在性能和安全性之间取得了平衡;no策略提供了最好的性能,但数据安全性最低。
61. RDB对主流程有什么影响?
回答
当执行阻塞式持久化的时候,由主进程进行RDB快照保存,会阻塞主进程。当执行后台持久化时,由fork出的子进程来进行RDB快照保存。如果数据量比较大的时候,会导致fork子进程这个操作比较耗时,从而阻塞主进程。由于采用了写时复制技术,如果在进行RDB快照保存的时候,有大量的写入操作执行,会导致主进程多拷贝一份数据,消耗大量额外的内存。
分析
阻塞式持久化。当执行save命令时,Redis会阻塞主进程,直到RDB文件创建完毕。这种方式会严重影响Redis的性能,特别是在数据量较大的情况下。
后台持久化。当执行bgsave命令时,Redis会创建一个子进程来执行RDB持久化,不会阻塞主进程。但是,fork子进程的过程可能会比较耗时,特别是在数据量较大的情况下,这会导致主进程短暂阻塞。
写时复制。在RDB快照过程中,如果主进程有大量的写入操作,会导致写时复制,主进程需要多拷贝一份数据,这会消耗大量的内存。因此,在RDB快照过程中,应该尽量避免大量的写入操作。
62. AOF对主流程有什么影响?
回答
当appendfsync使用always,如果AOF写入日志压力过大会导致主进程处理其他请求很慢。当appendfsync使用everysec,如果后台线程上一轮的fsync没有完成,会导致我们本轮主线程执行write被阻塞(直到fsync完成)。当AOF重写发生时,如果数据量比较大,会导致fork子进程这个操作比较耗时,从而阻塞主进程。
分析
always策略。在always策略下,Redis会在每个事件循环都将aof_buf缓冲区里的所有内容通过write写入AOF文件描述符,并且调用fsync同步它。这种方式在主线程中进行同步刷盘,会阻塞主线程,影响Redis的性能。
everysec策略。在everysec策略下,Redis会在每个事件循环都将aof_buf缓冲区的所有内容通过write写入到AOF文件描述符,并且每隔超过一秒就要在子线程中对AOF文件进行一次fsync同步。如果后台线程上一轮的fsync没有完成,主线程的write操作会被阻塞,直到fsync完成。
AOF重写。在AOF重写过程中,Redis会fork一个子进程来执行重写操作。如果数据量比较大,fork子进程的过程可能会比较耗时,导致主进程短暂阻塞。此外,在重写过程中,主进程需要将新的写入命令写入到AOF重写缓冲区,这也会消耗一定的CPU和内存资源。
63. AOF混合持久化方案是什么?
回答
AOF混合持久化会使用RDB持久化函数将内存数据写入到新的AOF文件中(数据格式也是RDB格式),而重写期间新的写入命令追加到新的AOF文件仍然是AOF格式。此时新的AOF文件就是由RDB格式和AOF格式组成的日志文件。
分析
混合持久化原理。AOF混合持久化是在AOF重写的基础上做了一些改动。在重写开始时,Redis会先使用RDB持久化函数将当前内存数据写入到新的AOF文件中,这部分数据以RDB格式存储。然后,在重写过程中,新的写入命令会以AOF格式追加到新的AOF文件中。这样,新的AOF文件就包含了RDB格式和AOF格式的数据。
优势。混合持久化结合了RDB和AOF的优点。RDB格式的数据提供了较快的恢复速度,而AOF格式的数据提供了更好的数据安全性。这种方式既保证了数据的安全性,又提供了较快的恢复速度。
使用场景。混合持久化特别适合需要快速恢复且对数据安全性要求较高的场景。例如,在系统重启后,Redis可以快速恢复RDB格式的数据,然后通过AOF格式的数据来保证数据的完整性。
64. 简单描述AOF重写流程
回答
当aof重写触发那一刻,主进程就会fork出一个子进程,然后这个子进程读取Redis DB中的数据,以字符串命令的格式写入到新AOF文件中。如果这个时候Redis接收到了新的写入命令,那么主进程会将这些"增量数据"写入到AOF重写缓冲区中。在子进程将数据都写入到新AOF文件后,主进程会通过管道将AOF重写缓冲区里面的数据发送给子进程,子进程再将这一份数据追加到新AOF文件中,保证新AOF文件的完整性。
分析
重写触发。AOF重写可以通过手动触发(bgrewriteaof命令)或自动触发(根据配置的AOF文件大小和增长率)。当重写触发时,主进程会fork出一个子进程来执行重写操作。
子进程重写。子进程会读取Redis DB中的数据,以字符串命令的格式写入到新的AOF文件中。这个过程不会阻塞主进程,主进程可以继续处理命令请求。
增量数据。在重写过程中,如果Redis接收到了新的写入命令,主进程会将这些命令写入到AOF重写缓冲区中。这个缓冲区用于存储重写期间的新写入命令。
数据同步。当子进程完成重写操作后,主进程会通过管道将AOF重写缓冲区中的数据发送给子进程,子进程将这些数据追加到新的AOF文件中。这样,新的AOF文件就包含了重写开始时的数据以及重写期间的新写入命令。
65. AOF重写你觉得有什么不足之处么?
回答
我认为主要有3点不足之处:
- 额外的CPU开销:在重写时,主进程需要将新的写入数据写入到AOF重写缓冲(aof_rewrite_buf),主进程需要通过管道向子进程发送AOF重写缓冲的数据,子进程还需要将这些数据写入到新的AOF日志中。
- 额外的内存开销:在重写时,AOF缓冲和AOF重写缓冲中的数据都是一样的(浪费了一份)。
- 额外的磁盘开销:在重写时,AOF缓冲需要刷入旧的AOF日志,AOF重写缓冲也需要刷入到新的AOF日志,导致在重写时磁盘多占一份数据。
分析
CPU开销。在AOF重写过程中,主进程需要将新的写入命令写入到AOF重写缓冲区,然后通过管道将数据发送给子进程,子进程还需要将这些数据写入到新的AOF文件中。这些操作都会消耗CPU资源,特别是在高并发场景下。
内存开销。在重写过程中,AOF缓冲区和AOF重写缓冲区中存储的是相同的数据,这导致了内存的浪费。特别是在数据量较大的情况下,这种浪费会更加明显。
磁盘开销。在重写过程中,AOF缓冲区需要将数据写入到旧的AOF文件,AOF重写缓冲区需要将数据写入到新的AOF文件,这导致了磁盘空间的浪费。特别是在AOF文件较大的情况下,这种浪费会更加明显。
66. 针对AOF重写的不足,你有什么优化思路呢?
回答
其实在Redis 7.0版本,就使用MP-AOF方案对AOF重写做了优化,核心其实就是去掉原来的重写缓冲,同时将AOF日志拆分为Base AOF日志和Incr AOF日志,由manifest来管理。重写时,还是开一个子进程,对Base AOF日志进行重写,但是新命令会往新的Incr AOF日志写,Incr AOF日志+新的Base AOF日志就构成了完整的新的AOF日志。
分析
MP-AOF方案。在Redis 7.0版本中,对AOF重写作出了优化,提出了MP-AOF(Multi Part AOF)方案。原来的AOF重写缓冲被移除,AOF日志也分成了Base AOF日志和Incr AOF日志。
Base AOF日志。Base AOF日志记录重写之前的命令,它是由子进程通过重写生成的。在重写过程中,子进程会读取当前内存数据,以命令的形式写入到新的Base AOF日志中。
Incr AOF日志。Incr AOF日志记录重写时新的写入命令。在重写过程中,主进程会将新的写入命令写入到aof_buf,然后将缓冲数据刷入到新的Incr AOF日志中。这样,新的Incr AOF日志和新的Base AOF日志就构成了完整的新的AOF日志。
Manifest文件。Manifest文件用于追踪管理AOF文件。当子进程完成重写后,主进程会更新manifest文件,将新生成的Base AOF和Incr AOF信息加入清单,并将之前的Base AOF和Incr AOF标记为HISTORY。这些HISTORY文件默认会被Redis异步删除(unlink),一旦manifest文件更新完成,就代表着整个AOFRW流程结束。