【Redis】五、Redis持久化、RDB和AOF-CSDN博客
问题
aof日志的实现
Redis在执行完一条写操作命令后,就会把该命令以追加的方式写入到一个文件里(aof都是主线程执行)
然后Redis重启时,会读取该文件记录的命令,然后逐一执行命令的方式来进行数据恢复。
AOF写其实不一定是直接写到磁盘。
- Redis 执行完写操作命令后,会将命令追加到 server.aof_buf 用户缓冲区;
- 然后通过 write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache,等待内核将数据写入硬盘;
- 具体内核缓冲区的数据什么时候写入到硬盘( 执行fsync()系统调用 ),由内核决定。
通过appendfsync
配置项,有三种策略,always,no和everysecond
RDB快照的实现
RDB 快照就是记录某一个瞬间的redis内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据。
因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。
实现:
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,他们的区别就在于是否在「主线程」里执行:
- 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程;
- 执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞;
AOF的重写
- 后台进程读取数据库中所有键值对,对每一个键值对用一条命令记录它的写入。
- 将日志文件变小,具有“多变一”的功能,旧日志文件中的多条命令,重写后在新日志中变成了一条命令。
RDB触发时机
- 配置文件中
save
规则满足,60s 内修改了 5次key(别看选项名叫 save,实际上执行的是 bgsave 命令) - 执行flushall命令
- redis关闭时
Redis宕机数据会丢失吗?
讲aof和rdb,最后讲4.0之后的混合模式
RDB是主进程执行吗,AOF是主进程执行吗,AOF是主进程执行吗?
RDB快照:save命令是主进程进行快照,bgsave是子进程进行快照,一般用bgsave子进程。
AOF日志:aof是主进程,aof主进程默认写到pageCache,如果通过参数设置每次都写到磁盘(写回策略),那么可能会阻塞主进程影响后续执行的修改命令( 影响性能,处理并发能力降低 ) 。
AOF重写:aof重写不是主进程,是后台进程。
RDB和AOF的比较
可以先看rdb和aof,再看这个。
RDB 比 AOF 优秀的地方:
- RDB比AOF文件更小,适合做数据的备份,灾难恢复。当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
- 与 AOF 相比,恢复大数据集的时候,RDB 速度更快。因为rdb是快照,而aof记录的命令需要一条一条执行。
AOF 比 RDB 优秀的地方:
- RDB 的数据安全性不如 AOF,没有办法实时或者秒级持久化数据。AOF 支持秒级数据丢失(取决 fsync 策略, 如果是 everysec,最多丢失 1 秒的数据)
- AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题。
总结
- 如果丢失一点数据 也没关系的话 可以用RDB
- 如果对数据安全性较高,那就rdb和aof都开启。
- 不建议只开启AOF,因为RDB快照可以数据备份、重启更快、还用于主从复制
RDB
RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据。
因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。
快照
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,他们的区别就在于是否在「主线程」里执行:
- 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程;
- 执行了 bgsave 命令,会fork一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞;
RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
Redis 的快照是全量快照,也就是说每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。
所以可以认为,执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。
执行快照时 数据能被修改吗?(先了解)
直接说结论吧,执行 bgsave 过程中,Redis 依然可以继续处理操作命令的,也就是数据是能被修改的。
那具体如何做到到呢?关键的技术就在于写时复制技术(Copy-On-Write, COW)。
通过 fork()
创建子进程,此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个。
创建 bgsave 子进程后,由于共享父进程的所有内存数据,于是就可以直接读取主线程(父进程)里的内存数据,并将数据写入到 RDB 文件。
但是,如果主线程(父进程)要修改共享数据里的某一块数据(比如键值对 A)时,就会发生写时复制,于是这块数据的物理内存就会被复制一份(键值对 A' ) ,然后主线程在这个数据副本(键值对 A' )进行修改操作。与此同时,bgsave 子进程可以继续把原来的数据(键值对 A )写入到 RDB 文件。
Redis 在使用 bgsave 快照过程中,如果主线程修改了内存数据,不管是否是共享的内存数据,RDB 快照都无法写入主线程刚修改的数据,因为此时主线程(父进程)的内存数据和子进程的内存数据已经分离了,子进程写入到 RDB 文件的内存数据只能是原本的内存数据。
如果系统恰好在 RDB 快照文件创建完毕后崩溃了,那么 Redis 将会丢失主线程在快照期间修改的数据。
AOF
Redis 每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里,然后重启 Redis 的时候,先去读取这个文件里的命令,并且执行它。
这种保存写操作命令到日志的持久化方式,就是 Redis 里的 AOF( Append Only File ) 持久化功能
Redis 是先执行写操作命令后,才将该命令记录到 AOF 日志里的。这么做其实有两个好处。
第一个好处,避免额外的检查开销。 只有在该命令执行成功后,才将命令记录到 AOF 日志里,这样就不用额外的检查开销,保证记录在 AOF 日志里的命令都是可执行并且正确的。
第二个好处,不会阻塞当前写操作命令的执行,因为当写操作命令执行成功后,才会将命令记录到 AOF 日志。
但是可能会给「下一个」命令带来阻塞风险。因为AOF 指向命令和记录日志是同步的,是主线程执行的日志,如果写磁盘特别慢会阻塞下一个命令。
那么怎么办?其实就是AOF日志写回硬盘时机的问题,有一下三种策略
三种写回策略
先说写回步骤
- Redis 执行完写操作命令后,会将命令追加到 server.aof_buf 缓冲区;
- 然后通过 write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache,等待内核将数据写入硬盘;
- 具体内核缓冲区的数据什么时候写入到硬盘( 执行fsync()系统调用 ),由内核决定。
Redis 提供了 3 种写回硬盘的策略,控制的就是上面说的第三步的过程。
在 redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:
- Always,这个单词的意思是**「总是」**,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
- Everysec,这个单词的意思是**「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘**;
- No,意味着不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
这 3 种写回策略都无法能完美解决「主进程阻塞」和「减少数据丢失」的问题,因为两个问题是对立的,偏向于一边的话,就会要牺牲另外一边
深入到源码后,你就会发现这三种策略只是在控制 fsync()
函数的调用时机。
aof重写机制
- 后台进程读取数据库中所有键值对,对每一个键值对用一条命令记录它的写入
- 将日志文件变小,具有“多变一”的功能,旧日志文件中的多条命令,重写后在新日志中变成了一条命令。
AOF 重写机制是在重写时,读取当前数据库中的所有键值对 ,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。
因为之前的aof是记录的修改命令,因为多个修改命令对同一个数据操作之后只有一个结果,所以对某个 数据重写后的命令只有一条 set key value。相当于把多个命令减少记录成一个命令。
重写机制的妙处在于,尽管某个键值对被多条写命令反复修改,最终也只需要根据这个「键值对」当前的最新状态,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令
后台进程重写
注意是后台进程重写,不是主进程。
Redis 的重写 AOF 过程是由后台子进程 bgrewriteaof 来完成的,这么做可以达到两个好处
- 子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程;
- 子进程带有主进程的数据副本(数据副本怎么产生的后面会说),这里使用子进程而不是线程,因为如果是使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。而使用子进程,创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,就会发生「写时复制 」,于是父子进程就有了独立的数据副本,就不用加锁来保证数据安全。
子进程的数据副本 和 写时复制 (先了解即可)
子进程是怎么拥有主进程一样的数据副本的呢?
主进程在通过 fork
系统调用生成 bgrewriteaof
子进程时,操作系统会把主进程的「页表」复制一份给子进程,这个页表记录着虚拟地址和物理地址映射关系,而不会复制物理内存,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。
这样一来,子进程就共享了父进程的物理内存数据了,这样能够节约物理内存资源,页表对应的页表项的属性会标记该物理内存的权限为只读。
不过,当父进程或者子进程在向这个内存发起写操作时,CPU 就会触发写保护中断,这个写保护中断是由于违反权限导致的,然后操作系统会在**「写保护中断处理函数」里进行物理内存的复制**,并重新设置其内存映射关系,将父子进程的内存读写权限设置为可读写,最后才会对内存进行写操作,这个过程被称为「写时复制( Copy On Write ) 」。
写时复制顾名思义,在发生写操作的时候,操作系统才会去复制物理内存,这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题。
不过,如果父进程的内存数据非常大,那自然页表也会很大,这时父进程在通过 fork 创建子进程的时候,阻塞的时间也越久。
所以,有两个阶段会导致阻塞父进程:
- fork创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
- 创建完子进程后,如果子进程或者父进程修改了共享数据,就会发生写时复制,这期间会拷贝物理内存,如果内存越大,自然阻塞的时间也越长;
子进程重写过程中,主进程依然可以正常处理命令。
如果此时主进程修改了已经存在 key-value,就会发生写时复制,注意这里只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的
还有个问题,重写 AOF 日志过程中,如果主进程修改了已经存在 key-value,此时这个 key-value 数据在子进程的内存数据就跟主进程的内存数据不一致了,为了解决这种数据不一致问题,Redis 设置了一个 AOF 重写缓冲区。
在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」。
也就是说,在 bgrewriteaof 子进程执行 AOF 重写期间,主进程 需要执行以下三个工作:
- 执行客户端发来的命令;
- 将执行后的写命令追加到 「AOF 缓冲区」;
- 将执行后的写命令追加到 「AOF 重写缓冲区」;